use std::{
ffi::{c_int, c_void, OsStr},
os::unix::ffi::OsStrExt,
path::Path,
};
use rustix::{
io::Errno,
process::{kill_process, Pid},
};
use crate::{FileIdentity, Signal};
const KINFO_PROC_SIZE: usize = 648;
const STARTTIME_SEC_OFFSET: usize = 0;
const STARTTIME_USEC_OFFSET: usize = 8;
const P_STAT_OFFSET: usize = 36;
const P_PID_OFFSET: usize = 40;
const SZOMB: u8 = 5;
#[repr(C, align(8))]
struct KinfoProc([u8; KINFO_PROC_SIZE]);
fn kinfo_proc(pid: u32) -> Option<KinfoProc> {
let pid = c_int::try_from(pid).ok()?;
let mut mib = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PID, pid];
let mut info = KinfoProc([0; KINFO_PROC_SIZE]);
let mut len: libc::size_t = KINFO_PROC_SIZE;
#[allow(unsafe_code)]
let result = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
mib.len() as libc::c_uint,
info.0.as_mut_ptr().cast::<c_void>(),
&mut len,
std::ptr::null_mut(),
0,
)
};
if result != 0 || len != KINFO_PROC_SIZE {
return None;
}
let reported_pid = i32::from_ne_bytes(info.0[P_PID_OFFSET..P_PID_OFFSET + 4].try_into().ok()?);
(reported_pid == pid).then_some(info)
}
pub(crate) fn exists(pid: u32) -> bool {
start_time(pid).is_some()
}
pub(crate) fn start_time(pid: u32) -> Option<u64> {
let info = kinfo_proc(pid)?;
if info.0[P_STAT_OFFSET] == SZOMB {
return None;
}
let seconds = i64::from_ne_bytes(
info.0[STARTTIME_SEC_OFFSET..STARTTIME_SEC_OFFSET + 8]
.try_into()
.ok()?,
);
let micros = i32::from_ne_bytes(
info.0[STARTTIME_USEC_OFFSET..STARTTIME_USEC_OFFSET + 4]
.try_into()
.ok()?,
);
let seconds = u64::try_from(seconds).ok()?;
let micros = u64::try_from(micros).ok()?;
seconds.checked_mul(1_000_000)?.checked_add(micros)
}
pub(crate) fn executable_identity(pid: u32) -> Option<FileIdentity> {
let pid = c_int::try_from(pid).ok()?;
let mut buffer = vec![0u8; libc::PROC_PIDPATHINFO_MAXSIZE as usize];
#[allow(unsafe_code)]
let written = unsafe {
libc::proc_pidpath(
pid,
buffer.as_mut_ptr().cast::<c_void>(),
buffer.len() as u32,
)
};
let written = usize::try_from(written).ok().filter(|&n| n > 0)?;
buffer.truncate(written.min(buffer.len()));
crate::file_identity(Path::new(OsStr::from_bytes(&buffer)))
}
pub(crate) fn signal(pid: u32, signal: Signal) -> Result<(), Errno> {
let raw = i32::try_from(pid)
.ok()
.and_then(Pid::from_raw)
.ok_or(Errno::SRCH)?;
let signal = match signal {
Signal::Terminate => rustix::process::Signal::TERM,
Signal::Kill => rustix::process::Signal::KILL,
};
kill_process(raw, signal)
}