jay-compositor 1.15.0

The Jay compositor
use crate::async_engine::AsyncEngine;
use crate::client::Client;
use crate::leaks::Tracker;
use crate::object::BreakLoops;
use crate::object::Version;
use crate::utils::bool_ext::BoolExt;
use crate::utils::client_trace::ClientTraceMessage;
use crate::utils::client_trace::ClientTraceWrite;
use crate::utils::copyhashmap::CopyHashMap;
use crate::utils::cow_ext::OptionCowExt;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::hash_map_ext::HashMapExt;
use crate::utils::numcell::NumCell;
use crate::utils::str_fmt::StrFmtUs;
use crate::wire::JayClientTraceId;
use crate::wire::ObjectId;
use crate::wire::jay_client_trace::*;
use bincode::Options;
use jay_algorithms::oserror::OsError;
use jay_algorithms::oserror::OsErrorExt2;
use jay_config::_private::bincode_ops;
use jay_proc::Object;
use jay_proc::StrFmt;
use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
use std::cell::Cell;
use std::convert::Infallible;
use std::io;
use std::io::Write;
use std::rc::Rc;
use thiserror::Error;
use uapi::OwnedFd;
use uapi::c;

#[derive(Debug, Serialize, Deserialize, StrFmt)]
pub struct ClientTraceInfo<'a> {
    pub id: u64,
    sandboxed: bool,
    sandbox_engine: Option<Cow<'a, str>>,
    sandbox_app_id: Option<Cow<'a, str>>,
    sandbox_instance_id: Option<Cow<'a, str>>,
    uid: c::uid_t,
    pid: c::pid_t,
    is_xwayland: bool,
    comm: Cow<'a, str>,
    exe: Cow<'a, str>,
    tag: Option<Cow<'a, str>>,
    connect_time_us: StrFmtUs,
    now_us: StrFmtUs,
}

#[derive(Debug, Error)]
enum ClientTraceInfoError {
    #[error("Could not serialize client info")]
    Serialize(#[source] bincode::Error),
    #[error("Could not create memfd")]
    Memfd(#[source] OsError),
    #[error("Could not write memfd")]
    Write(#[source] io::Error),
}

pub struct ClientTracers {
    ids: ClientTracerIds,
    eng: Rc<AsyncEngine>,
    pub num: NumCell<usize>,
    map: CopyHashMap<ClientTracerId, Rc<JayClientTrace>>,
}

linear_ids!(ClientTracerIds, ClientTracerId, u64);

struct TraceTarget {
    id: ClientTracerId,
    client: Rc<Client>,
}

#[derive(Object)]
#[break_loops]
pub struct JayClientTrace {
    id: JayClientTraceId,
    client: Rc<Client>,
    target: Cell<Option<TraceTarget>>,
    tracker: Tracker<Self>,
    version: Version,
    sink: Option<ClientTraceWrite>,
}

impl ClientTracers {
    pub fn new(eng: &Rc<AsyncEngine>) -> Self {
        Self {
            ids: Default::default(),
            eng: eng.clone(),
            num: Default::default(),
            map: Default::default(),
        }
    }

    #[cold]
    pub fn handle_message(&self, id: ObjectId, msg: &dyn ClientTraceMessage) {
        let now_us = self.eng.now_rt().usec();
        for trace in self.map.lock().values() {
            if let Some(sink) = &trace.sink {
                sink.write_msg(id, now_us, msg);
            }
        }
    }

    #[cold]
    pub fn handle_delete_id(&self, id: ObjectId) {
        for trace in self.map.lock().values() {
            if let Some(sink) = &trace.sink {
                sink.write_delete_id(id);
            }
        }
    }

    pub fn handle_disconnected(&self) {
        for debugger in self.map.clear().drain_values() {
            debugger.send_disconnected();
            debugger.target.take();
        }
        self.num.set(0);
    }
}

impl ClientTraceInfo<'_> {
    fn create(target: &Rc<Client>) -> Result<Rc<OwnedFd>, ClientTraceInfoError> {
        let info = ClientTraceInfo {
            id: target.id.raw(),
            sandboxed: target.acceptor.sandboxed,
            sandbox_engine: target.acceptor.sandbox_engine.as_deref().borrowed(),
            sandbox_app_id: target.acceptor.app_id.as_deref().borrowed(),
            sandbox_instance_id: target.acceptor.instance_id.as_deref().borrowed(),
            uid: target.pid_info.uid,
            pid: target.pid_info.pid,
            is_xwayland: target.is_xwayland,
            comm: target.pid_info.comm.as_str().into(),
            exe: target.pid_info.exe.as_str().into(),
            tag: target.acceptor.tag.as_deref().borrowed(),
            connect_time_us: StrFmtUs(target.connect_time_us),
            now_us: StrFmtUs(target.state.now_usec_rt()),
        };
        let info = bincode_ops()
            .serialize(&info)
            .map_err(ClientTraceInfoError::Serialize)?;
        let mut memfd = uapi::memfd_create("client-info", c::MFD_CLOEXEC)
            .map_os_err(ClientTraceInfoError::Memfd)?;
        memfd
            .write_all(&info)
            .map_err(ClientTraceInfoError::Write)?;
        Ok(Rc::new(memfd))
    }
}

impl JayClientTrace {
    pub fn install(
        id: JayClientTraceId,
        client: &Rc<Client>,
        target: Option<&Rc<Client>>,
        version: Version,
        server: bool,
    ) {
        let state = &client.state;
        let mut error = String::new();
        let sink = target.is_some().and_then(|| {
            ClientTraceWrite::new(&state.ring)
                .inspect_err(|e| {
                    error = format!("Could not create ClientTraceWrite: {}", ErrorFmt(e));
                    log::error!("{error}");
                })
                .ok()
        });
        let info = target.and_then(|target| {
            ClientTraceInfo::create(target)
                .inspect_err(|e| {
                    error = format!("Could not create client info: {}", ErrorFmt(e));
                    log::error!("{error}");
                })
                .ok()
        });
        let slf = Rc::new(Self {
            id,
            client: client.clone(),
            target: Default::default(),
            tracker: Default::default(),
            version,
            sink,
        });
        track!(client, slf);
        if server {
            client.add_server_obj(&slf);
        } else {
            client.add_client_obj(&slf);
        }
        if let Some(target) = target
            && let Some(sink) = &slf.sink
            && let Some(info) = &info
        {
            let target = TraceTarget {
                id: target.tracers.ids.next(),
                client: target.clone(),
            };
            slf.send_info(info);
            slf.send_storage(unsafe { sink.memfd() });
            target.client.tracers.map.set(target.id, slf.clone());
            target.client.tracers.num.fetch_add(1);
            slf.target.set(Some(target));
        } else if target.is_none() {
            slf.send_failed("Client does not exist");
        } else {
            slf.send_failed(&error);
        }
    }

    fn send_failed(&self, msg: &str) {
        self.client.event(Failed {
            self_id: self.id,
            msg,
        });
    }

    fn send_disconnected(&self) {
        self.client.event(Disconnected { self_id: self.id });
    }

    fn send_info(&self, fd: &Rc<OwnedFd>) {
        self.client.event(Info {
            self_id: self.id,
            fd: fd.clone(),
        });
    }

    fn send_storage(&self, fd: &Rc<OwnedFd>) {
        self.client.event(Storage {
            self_id: self.id,
            fd: fd.clone(),
        });
    }

    fn detach(&self) {
        if let Some(target) = self.target.take() {
            target.client.tracers.map.remove(&target.id);
            target.client.tracers.num.fetch_sub(1);
        }
    }
}

impl JayClientTraceRequestHandler for JayClientTrace {
    type Error = Infallible;

    fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        self.detach();
        self.client.remove_obj(self);
        Ok(())
    }
}

impl BreakLoops for JayClientTrace {
    fn break_loops(self: Rc<Self>) {
        self.detach();
    }
}