aboutsummaryrefslogtreecommitdiff
path: root/crates/dinggerd/src/platform.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/dinggerd/src/platform.rs')
-rw-r--r--crates/dinggerd/src/platform.rs68
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()])
+ );
+ }
+}