aboutsummaryrefslogtreecommitdiff
path: root/crates/dinggerd/src/platform.rs
blob: 8504a830d652ebf929de4a3bb61a1edfb053f8ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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()])
        );
    }
}