const CTL_KERN: libc::c_int = 1;
const KERN_PROCARGS2: libc::c_int = 49;
pub fn read_process_argv(pid: u32) -> std::io::Result<Vec<std::ffi::OsString>> {
if pid == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"pid 0 is the kernel scheduler  not queryable",
));
}
let mut name: [libc::c_int; 3] = [CTL_KERN, KERN_PROCARGS2, pid as libc::c_int];
let mut len: libc::size_t = 0;
let r = unsafe {
libc::sysctl(
name.as_mut_ptr(),
3,
std::ptr::null_mut(),
&mut len,
std::ptr::null_mut(),
0,
)
};
if r != 0 {
return Err(std::io::Error::last_os_error());
}
if len < std::mem::size_of::<i32>() {
return Err(std::io::Error::other(format!(
"KERN_PROCARGS2 returned size={len}, smaller than argc header",
)));
}
let mut buf = vec![0u8; len];
let r = unsafe {
libc::sysctl(
name.as_mut_ptr(),
3,
buf.as_mut_ptr() as *mut libc::c_void,
&mut len,
std::ptr::null_mut(),
0,
)
};
if r != 0 {
return Err(std::io::Error::last_os_error());
}
buf.truncate(len);
parse_procargs2_argv(&buf)
}
pub fn read_process_cmdline(pid: u32) -> std::io::Result<String> {
Ok(render_display(&read_process_argv(pid)?))
}
fn parse_procargs2_argv(buf: &[u8]) -> std::io::Result<Vec<std::ffi::OsString>> {
use std::os::unix::ffi::OsStringExt;
if buf.len() < std::mem::size_of::<i32>() {
return Ok(Vec::new());
}
let argc = i32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]);
if argc <= 0 {
return Ok(Vec::new());
}
let mut cursor = std::mem::size_of::<i32>();
while cursor < buf.len() && buf[cursor] != 0 {
cursor += 1;
}
while cursor < buf.len() && buf[cursor] == 0 {
cursor += 1;
}
let mut argv = Vec::with_capacity(argc as usize);
for _ in 0..argc {
if cursor >= buf.len() {
break;
}
let start = cursor;
while cursor < buf.len() && buf[cursor] != 0 {
cursor += 1;
}
argv.push(std::ffi::OsString::from_vec(buf[start..cursor].to_vec()));
cursor = cursor.saturating_add(1);
}
Ok(argv)
}
fn render_display(argv: &[std::ffi::OsString]) -> String {
argv.iter()
.map(|argument| argument.to_string_lossy())
.collect::<Vec<_>>()
.join(" ")
}
#[cfg(test)]
mod tests {
use super::{parse_procargs2_argv, render_display};
fn build_procargs2(exec_path: &str, argv: &[&str]) -> Vec<u8> {
let mut buf = Vec::new();
let argc = argv.len() as i32;
buf.extend_from_slice(&argc.to_ne_bytes());
buf.extend_from_slice(exec_path.as_bytes());
buf.push(0);
while buf.len() % 8 != 0 {
buf.push(0);
}
for arg in argv {
buf.extend_from_slice(arg.as_bytes());
buf.push(0);
}
buf
}
#[test]
fn parses_argv_skipping_exec_path_and_padding() {
let buf = build_procargs2("/usr/bin/myprog", &["myprog", "--flag", "value with space"]);
let out = parse_procargs2_argv(&buf).expect("parse");
assert_eq!(
out,
["myprog", "--flag", "value with space"].map(std::ffi::OsString::from)
);
}
#[test]
fn empty_argv_yields_empty_string() {
let buf = build_procargs2("/usr/bin/noop", &[]);
let out = parse_procargs2_argv(&buf).expect("parse");
assert!(out.is_empty());
}
#[test]
fn argc_zero_short_circuits() {
let mut buf = 0i32.to_ne_bytes().to_vec();
buf.extend_from_slice(b"/usr/bin/noop\0");
let out = parse_procargs2_argv(&buf).expect("parse");
assert!(out.is_empty());
}
#[test]
fn keeps_spaces_quotes_empty_arguments_and_backslashes() {
let buf = build_procargs2(
"/usr/bin/tool",
&["tool", "has space", "quote\"", "", r"back\slash"],
);
let argv = parse_procargs2_argv(&buf).expect("parse");
assert_eq!(
argv,
["tool", "has space", "quote\"", "", r"back\slash"].map(std::ffi::OsString::from)
);
assert_eq!(render_display(&argv), "tool has space quote\" back\\slash");
}
}