diff --git a/mux/Cargo.toml b/mux/Cargo.toml index db05fd61a..2bbcb5271 100644 --- a/mux/Cargo.toml +++ b/mux/Cargo.toml @@ -38,7 +38,7 @@ url = "2" wezterm-ssh = { path = "../wezterm-ssh" } wezterm-term = { path = "../term", features=["use_serde"] } -[target.'cfg(any(windows, target_os="linux", target_os="macos"))'.dependencies] +[target.'cfg(any(windows, target_os="linux"))'.dependencies] sysinfo = "0.22" [target."cfg(windows)".dependencies] diff --git a/mux/src/localpane.rs b/mux/src/localpane.rs index c8dd24390..d96c491d0 100644 --- a/mux/src/localpane.rs +++ b/mux/src/localpane.rs @@ -838,11 +838,16 @@ impl LocalPane { None } - fn divine_process_list(&self, force_refresh: bool) -> Option { - #[cfg(any(windows, target_os = "linux", target_os = "macos"))] + fn divine_process_list(&self, _force_refresh: bool) -> Option { + #[cfg(target_os = "macos")] + if let ProcessState::Running { pid: Some(pid), .. } = &*self.process.borrow() { + return dbg!(LocalProcessInfo::with_root_pid_macos(*pid)); + } + + #[cfg(any(windows, target_os = "linux"))] if let ProcessState::Running { pid: Some(pid), .. } = &*self.process.borrow() { return LocalProcessInfo::with_root_pid( - &*if force_refresh { + &*if _force_refresh { crate::sysinfo::get_with_forced_refresh() } else { crate::sysinfo::get() diff --git a/mux/src/procinfo.rs b/mux/src/procinfo.rs index 78d4e1d43..a3b72e217 100644 --- a/mux/src/procinfo.rs +++ b/mux/src/procinfo.rs @@ -18,7 +18,7 @@ pub enum LocalProcessStatus { Unknown, } -#[cfg(any(windows, target_os = "linux", target_os = "macos"))] +#[cfg(any(windows, target_os = "linux"))] impl LocalProcessStatus { fn from_process_status(status: sysinfo::ProcessStatus) -> Self { match status { @@ -70,7 +70,208 @@ impl LocalProcessInfo { } } -#[cfg(any(windows, target_os = "linux", target_os = "macos"))] +#[cfg(target_os = "macos")] +impl LocalProcessInfo { + pub(crate) fn with_root_pid_macos(pid: u32) -> Option { + /// Enumerate all current process identifiers + fn all_pids() -> Vec { + let num_pids = unsafe { libc::proc_listallpids(std::ptr::null_mut(), 0) }; + if num_pids < 1 { + return vec![]; + } + + // Give a bit of padding to avoid looping if processes are spawning + // rapidly while we're trying to collect this info + const PADDING: usize = 32; + let mut pids: Vec = Vec::with_capacity(num_pids as usize + PADDING); + loop { + let n = unsafe { + libc::proc_listallpids( + pids.as_mut_ptr() as *mut _, + (pids.capacity() * std::mem::size_of::()) as _, + ) + }; + + if n < 1 { + return vec![]; + } + + let n = n as usize; + + if n > pids.capacity() { + pids.reserve(n + PADDING); + continue; + } + + unsafe { pids.set_len(n) }; + return pids; + } + } + + /// Obtain info block for a pid. + /// Note that the process could have gone away since we first + /// observed the pid and the time we call this, so we must + /// be able to tolerate this failing. + fn info_for_pid(pid: libc::pid_t) -> Option { + let mut info: libc::proc_bsdinfo = unsafe { std::mem::zeroed() }; + let wanted_size = std::mem::size_of::() as _; + let res = unsafe { + libc::proc_pidinfo( + pid, + libc::PROC_PIDTBSDINFO, + 0, + &mut info as *mut _ as *mut _, + wanted_size, + ) + }; + + if res == wanted_size { + Some(info) + } else { + None + } + } + + fn cwd_for_pid(pid: libc::pid_t) -> PathBuf { + let mut pathinfo: libc::proc_vnodepathinfo = unsafe { std::mem::zeroed() }; + let size = std::mem::size_of_val(&pathinfo) as libc::c_int; + let ret = unsafe { + libc::proc_pidinfo( + pid, + libc::PROC_PIDVNODEPATHINFO, + 0, + &mut pathinfo as *mut _ as *mut _, + size, + ) + }; + if ret == size { + let path = + unsafe { std::ffi::CStr::from_ptr(pathinfo.pvi_cdir.vip_path.as_ptr() as _) }; + path.to_str().unwrap_or("").into() + } else { + PathBuf::new() + } + } + + fn exe_and_args_for_pid_sysctl(pid: libc::pid_t) -> Option<(PathBuf, Vec)> { + use libc::c_int; + let mut size = 64 * 1024; + let mut buf: Vec = Vec::with_capacity(size); + let mut mib = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid as c_int]; + + let res = unsafe { + libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + buf.as_mut_ptr() as *mut _, + &mut size, + std::ptr::null_mut(), + 0, + ) + }; + if res == -1 { + return None; + } + if size < (std::mem::size_of::() * 2) { + // Not big enough + return None; + } + unsafe { buf.set_len(size) }; + + // The data in our buffer is laid out like this: + // argc - c_int + // exe_path - NUL terminated string + // argv[0] - NUL terminated string + // argv[1] - NUL terminated string + // ... + // argv[n] - NUL terminated string + // envp[0] - NUL terminated string + // ... + + let mut ptr = &buf[0..size]; + + let argc: c_int = unsafe { std::ptr::read(ptr.as_ptr() as *const c_int) }; + ptr = &ptr[std::mem::size_of::()..]; + + fn consume_cstr(ptr: &mut &[u8]) -> Option { + let nul = ptr.iter().position(|&c| c == 0)?; + let s = String::from_utf8_lossy(&ptr[0..nul]).to_owned().to_string(); + *ptr = ptr.get(nul + 1..)?; + Some(s) + } + + let exe_path = consume_cstr(&mut ptr)?.into(); + + let mut args = vec![]; + for _ in 0..argc { + args.push(consume_cstr(&mut ptr)?); + } + + Some((exe_path, args)) + } + + fn exe_for_pid(pid: libc::pid_t) -> PathBuf { + let mut buffer: Vec = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _); + let x = unsafe { + libc::proc_pidpath( + pid, + buffer.as_mut_ptr() as *mut _, + libc::PROC_PIDPATHINFO_MAXSIZE as _, + ) + }; + if x > 0 { + unsafe { buffer.set_len(x as usize) }; + String::from_utf8_lossy(&buffer) + .to_owned() + .to_string() + .into() + } else { + PathBuf::new() + } + } + + let procs: Vec<_> = all_pids().into_iter().filter_map(info_for_pid).collect(); + + fn build_proc(info: &libc::proc_bsdinfo, procs: &[libc::proc_bsdinfo]) -> LocalProcessInfo { + let mut children = HashMap::new(); + + for kid in procs { + if kid.pbi_ppid == info.pbi_pid { + children.insert(kid.pbi_pid, build_proc(kid, procs)); + } + } + + let (executable, argv) = exe_and_args_for_pid_sysctl(info.pbi_pid as _) + .unwrap_or_else(|| (exe_for_pid(info.pbi_pid as _), vec![])); + + let name = executable + .file_name() + .and_then(|e| e.to_str()) + .unwrap_or("") + .to_string(); + + LocalProcessInfo { + pid: info.pbi_pid, + ppid: info.pbi_ppid, + name, + executable, + cwd: cwd_for_pid(info.pbi_pid as _), + argv, + start_time: info.pbi_start_tvsec, + status: LocalProcessStatus::Idle, + children, + } + } + + if let Some(info) = procs.iter().find(|info| info.pbi_pid == pid) { + Some(build_proc(info, &procs)) + } else { + None + } + } +} + +#[cfg(any(windows, target_os = "linux"))] impl LocalProcessInfo { pub(crate) fn with_root_pid(system: &sysinfo::System, pid: u32) -> Option { use sysinfo::{AsU32, Pid, Process, ProcessExt, SystemExt}; diff --git a/mux/src/sysinfo.rs b/mux/src/sysinfo.rs index de2d22ce9..128eab700 100644 --- a/mux/src/sysinfo.rs +++ b/mux/src/sysinfo.rs @@ -1,4 +1,4 @@ -#![cfg(any(windows, target_os = "linux", target_os = "macos"))] +#![cfg(any(windows, target_os = "linux"))] use std::sync::{Mutex, MutexGuard}; use std::time::{Duration, Instant}; use sysinfo::{ProcessRefreshKind, RefreshKind, System, SystemExt};