diff options
Diffstat (limited to 'crates/dinggerd/src/platform.rs')
| -rw-r--r-- | crates/dinggerd/src/platform.rs | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/crates/dinggerd/src/platform.rs b/crates/dinggerd/src/platform.rs new file mode 100644 index 0000000..8504a83 --- /dev/null +++ b/crates/dinggerd/src/platform.rs @@ -0,0 +1,68 @@ +//! macOS process inspection. + +/// 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<Vec<String>> { + 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<String> = 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<Vec<String>> { + 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()]) + ); + } +} |
