//! macOS process inspection and process setup. /// Exit quietly when stdout closes early (`dingger restore --list | head`), /// like other command-line tools, instead of panicking: Rust ignores /// SIGPIPE by default. #[cfg(unix)] pub fn default_sigpipe() { extern "C" { fn signal(signum: std::os::raw::c_int, handler: usize) -> usize; } const SIGPIPE: std::os::raw::c_int = 13; const SIG_DFL: usize = 0; // SAFETY: resets one signal to its default disposition before any // threads start. unsafe { signal(SIGPIPE, SIG_DFL); } } /// A process's exact argument vector, from the kernel (`KERN_PROCARGS2`): /// unlike `ps`, it keeps arguments that contain spaces intact. #[cfg(target_os = "macos")] pub fn process_argv(pid: u32) -> Option> { use std::os::raw::{c_int, c_uint, c_void}; extern "C" { fn sysctl( name: *mut c_int, namelen: c_uint, oldp: *mut c_void, oldlenp: *mut usize, newp: *mut c_void, newlen: usize, ) -> c_int; } const CTL_KERN: c_int = 1; const KERN_PROCARGS2: c_int = 49; let mut mib = [CTL_KERN, KERN_PROCARGS2, pid as c_int]; let mut buffer = vec![0u8; 256 * 1024]; let mut size = buffer.len(); // SAFETY: mib has 3 entries; buffer is writable for `size` bytes, and the // kernel writes at most that many, updating `size`. let status = unsafe { sysctl(mib.as_mut_ptr(), 3, buffer.as_mut_ptr().cast(), &mut size, std::ptr::null_mut(), 0) }; if status != 0 || size < 4 { return None; } let data = &buffer[..size]; let argc = i32::from_ne_bytes(data[..4].try_into().ok()?).max(0) as usize; // Layout: argc, the executable path, NUL padding, then argv strings. let rest = &data[4..]; let after_path = rest.iter().position(|&b| b == 0)?; let args_start = after_path + rest[after_path..].iter().position(|&b| b != 0)?; let argv: Vec = rest[args_start..] .split(|&b| b == 0) .take(argc) .map(|arg| String::from_utf8_lossy(arg).into_owned()) .collect(); (argv.len() == argc).then_some(argv) } #[cfg(not(target_os = "macos"))] pub fn process_argv(_pid: u32) -> Option> { None } #[cfg(test)] mod tests { #[test] fn reads_a_processs_exact_arguments() { // A compound command keeps sh alive (it would exec a lone command). let mut child = std::process::Command::new("/bin/sh") .args(["-c", "sleep 30; :", "two words"]) .spawn() .unwrap_or_else(|err| panic!("spawn: {err}")); std::thread::sleep(std::time::Duration::from_millis(100)); let argv = super::process_argv(child.id()); let _ = child.kill(); let _ = child.wait(); assert_eq!( argv, Some(vec!["/bin/sh".into(), "-c".into(), "sleep 30; :".into(), "two words".into()]) ); } }