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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
//! 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<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()])
);
}
}
|