#[must_use]
pub fn observe_parent() -> Option<String> {
let ppid = parent_pid()?;
match process_name(ppid) {
Some(name) if !name.is_empty() => Some(format!("{name} (pid {ppid})")),
_ => Some(format!("pid {ppid}")),
}
}
#[cfg(unix)]
fn parent_pid() -> Option<i32> {
let ppid = unsafe { libc::getppid() };
if ppid > 0 { Some(ppid) } else { None }
}
#[cfg(not(unix))]
fn parent_pid() -> Option<i32> {
None
}
#[cfg(target_os = "macos")]
fn process_name(pid: i32) -> Option<String> {
const PROC_PIDPATHINFO_MAXSIZE: usize = 4096;
unsafe extern "C" {
fn proc_pidpath(
pid: libc::c_int,
buffer: *mut libc::c_void,
buffersize: u32,
) -> libc::c_int;
}
let mut buf = vec![0u8; PROC_PIDPATHINFO_MAXSIZE];
let n = unsafe {
proc_pidpath(
pid as libc::c_int,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len() as u32,
)
};
if n <= 0 {
return None;
}
buf.truncate(n as usize);
String::from_utf8(buf).ok().filter(|s| !s.is_empty())
}
#[cfg(all(unix, not(target_os = "macos")))]
fn process_name(pid: i32) -> Option<String> {
if let Ok(exe) = std::fs::read_link(format!("/proc/{pid}/exe")) {
if let Some(s) = exe.to_str() {
if !s.is_empty() {
return Some(s.to_string());
}
}
}
std::fs::read_to_string(format!("/proc/{pid}/comm"))
.ok()
.map(|s| s.trim_end().to_string())
.filter(|s| !s.is_empty())
}
#[cfg(not(unix))]
fn process_name(_pid: i32) -> Option<String> {
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn observe_parent_returns_non_empty_identity_with_pid() {
let id = observe_parent().expect("a parent process should be observable on the test host");
assert!(!id.is_empty());
assert!(
id.contains("pid "),
"identity should always carry the observed pid, got {id:?}"
);
}
#[test]
fn observed_identity_is_a_clean_single_line() {
let id = observe_parent().unwrap();
assert!(!id.contains('\0'));
assert!(!id.contains('\n'));
}
}