use std::process::Command;
use std::time::Duration;
use systemprompt_identifiers::ServiceName;
#[expect(
unsafe_code,
reason = "std::os::unix::process::CommandExt::pre_exec is an unsafe fn; there is no safe way \
to run code in the forked child before exec, and the parent-death signal must be \
armed there to cover children that never opt in"
)]
pub(super) fn arm_parent_death_signal(cmd: &mut Command) {
use std::os::unix::process::CommandExt;
let supervisor = std::process::id();
unsafe {
cmd.pre_exec(move || {
if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) != 0 {
return Err(std::io::Error::last_os_error());
}
if libc::getppid() != supervisor as libc::pid_t {
libc::_exit(0);
}
Ok(())
});
}
}
const IDENTITY_ATTEMPTS: u32 = 20;
const IDENTITY_RETRY: Duration = Duration::from_millis(5);
#[must_use]
pub fn live_pid_is_subprocess(pid: u32, name_key: &str, service_name: &ServiceName) -> bool {
live_environ(pid).is_some_and(|environ| {
systemprompt_models::subprocess::environ_identifies_child(&environ, name_key, service_name)
})
}
pub(super) fn live_environ(pid: u32) -> Option<Vec<u8>> {
for attempt in 1..=IDENTITY_ATTEMPTS {
let settling = attempt < IDENTITY_ATTEMPTS && process_present(pid) && !is_zombie(pid);
match std::fs::read(format!("/proc/{pid}/environ")) {
Ok(environ) if !environ.is_empty() => return Some(environ),
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => return None,
Ok(_) | Err(_) if settling => std::thread::sleep(IDENTITY_RETRY),
Ok(_) => return None,
Err(e) => {
tracing::warn!(pid, error = %e, "Could not read process environ to verify child identity");
return None;
},
}
}
None
}
fn process_present(pid: u32) -> bool {
std::path::Path::new(&format!("/proc/{pid}")).exists()
}
#[must_use]
pub fn is_zombie(pid: u32) -> bool {
let Ok(stat) = std::fs::read_to_string(format!("/proc/{pid}/stat")) else {
return false;
};
let Some((_, after_comm)) = stat.rsplit_once(')') else {
return false;
};
after_comm.split_whitespace().next() == Some("Z")
}