use std::{io, os::fd::OwnedFd};
use rustix::{
event::{poll, PollFd, PollFlags, Timespec},
io::Errno,
process::{kill_process, pidfd_open, pidfd_send_signal, Pid, PidfdFlags},
};
use crate::{FileIdentity, Signal};
fn raw_pid(pid: u32) -> Option<Pid> {
i32::try_from(pid).ok().and_then(Pid::from_raw)
}
pub(crate) fn open(pid: u32) -> io::Result<Option<Option<OwnedFd>>> {
let Some(raw) = raw_pid(pid) else {
return Ok(None);
};
match pidfd_open(raw, PidfdFlags::empty()) {
Ok(pidfd) => Ok(Some(Some(pidfd))),
Err(Errno::SRCH) => Ok(None),
Err(_) => Ok(start_time(pid).map(|_| None)),
}
}
pub(crate) fn pidfd_alive(pidfd: Option<&OwnedFd>) -> bool {
let Some(pidfd) = pidfd else {
return true;
};
let mut fds = [PollFd::new(pidfd, PollFlags::IN)];
match poll(&mut fds, Some(&Timespec::default())) {
Ok(0) => true,
Ok(_) => false,
Err(_) => true,
}
}
pub(crate) fn start_time(pid: u32) -> Option<u64> {
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
start_time_from_stat(&stat)
}
pub(crate) fn start_time_from_stat(stat: &str) -> Option<u64> {
let mut fields = stat.rsplit_once(')')?.1.split_whitespace();
let state = fields.next()?;
if matches!(state, "Z" | "X" | "x") {
return None;
}
fields.nth(18)?.parse().ok()
}
pub(crate) fn executable_identity(pid: u32) -> Option<FileIdentity> {
crate::file_identity(std::path::Path::new(&format!("/proc/{pid}/exe")))
}
pub(crate) fn signal(pid: u32, pidfd: Option<&OwnedFd>, signal: Signal) -> Result<(), Errno> {
let signal = match signal {
Signal::Terminate => rustix::process::Signal::TERM,
Signal::Kill => rustix::process::Signal::KILL,
};
match pidfd {
Some(pidfd) => pidfd_send_signal(pidfd, signal),
None => kill_process(raw_pid(pid).ok_or(Errno::SRCH)?, signal),
}
}
#[cfg(test)]
mod tests {
use super::start_time_from_stat;
#[test]
fn stat_parser_counts_fields_from_the_last_parenthesis() {
let stat = "123 (foo) bar) S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 424242 20";
assert_eq!(start_time_from_stat(stat), Some(424242));
}
#[test]
fn stat_parser_reads_a_zombie_as_exited() {
let stat = "123 (foo) Z 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 424242 20";
assert_eq!(start_time_from_stat(stat), None);
}
}