use std::io;
use systemprompt_identifiers::ServiceName;
#[derive(Debug, thiserror::Error)]
#[error("KERN_ARGMAX is unusable")]
struct UnusableArgMax(#[source] std::num::TryFromIntError);
#[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>> {
let pid = i32::try_from(pid).ok()?;
match process_args_blob(pid) {
Ok(blob) => {
systemprompt_models::subprocess::environ_from_procargs2(&blob).map(<[u8]>::to_vec)
},
Err(e) => {
tracing::warn!(pid, error = %e, "Could not read process environ to verify child identity");
None
},
}
}
#[must_use]
#[expect(
unsafe_code,
reason = "proc_pidinfo is the only interface exposing a process state on Darwin; libc \
publishes no kinfo_proc for Apple, so there is no safe wrapper to call instead"
)]
pub fn is_zombie(pid: u32) -> bool {
let Ok(pid) = i32::try_from(pid) else {
return false;
};
let Ok(want) = libc::c_int::try_from(size_of::<libc::proc_bsdshortinfo>()) else {
return false;
};
let mut info = std::mem::MaybeUninit::<libc::proc_bsdshortinfo>::zeroed();
let written = unsafe {
libc::proc_pidinfo(
pid,
libc::PROC_PIDT_SHORTBSDINFO,
0,
info.as_mut_ptr().cast(),
want,
)
};
if written != want {
return written == 0
&& io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH)
&& pid_allocated(pid);
}
let info = unsafe { info.assume_init() };
info.pbsi_status == libc::SZOMB
}
fn pid_allocated(pid: libc::pid_t) -> bool {
match nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), None) {
Ok(()) | Err(nix::errno::Errno::EPERM) => true,
Err(_) => false,
}
}
#[expect(
unsafe_code,
reason = "sysctl is the only interface exposing another process's environment on Darwin; \
there is no safe wrapper for KERN_PROCARGS2 in the dependency set"
)]
fn process_args_blob(pid: libc::c_int) -> io::Result<Vec<u8>> {
let mut mib = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid];
let mut buf = vec![0u8; arg_max()?];
let mut len = buf.len();
let rc = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
3,
buf.as_mut_ptr().cast(),
&raw mut len,
std::ptr::null_mut(),
0,
)
};
if rc != 0 {
return Err(io::Error::last_os_error());
}
buf.truncate(len);
Ok(buf)
}
#[expect(
unsafe_code,
reason = "sizing the KERN_PROCARGS2 buffer needs KERN_ARGMAX, which is only readable through \
the same raw sysctl interface"
)]
fn arg_max() -> io::Result<usize> {
let mut mib = [libc::CTL_KERN, libc::KERN_ARGMAX];
let mut value: libc::c_int = 0;
let mut len = size_of::<libc::c_int>();
let rc = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
2,
(&raw mut value).cast(),
&raw mut len,
std::ptr::null_mut(),
0,
)
};
if rc != 0 {
return Err(io::Error::last_os_error());
}
usize::try_from(value).map_err(|e| io::Error::other(UnusableArgMax(e)))
}