use std::sync::OnceLock;
use std::time::{Duration, Instant};
pub const SWEEP_BUDGET_MS: u64 = 2_000;
const SWEEP_POLL: Duration = Duration::from_millis(10);
static FS_GUARD: OnceLock<(i32, i32)> = OnceLock::new();
pub fn arm_exit_sweep(root_pid: i32, pgid: i32) {
if FS_GUARD.set((root_pid, pgid)).is_err() {
return; }
unsafe { libc::atexit(exit_sweep) };
}
extern "C" fn exit_sweep() {
let Some(&(root_pid, pgid)) = FS_GUARD.get() else {
return;
};
unsafe { libc::kill(-pgid, libc::SIGKILL) };
sweep_reparented(SWEEP_BUDGET_MS, root_pid);
}
pub fn sweep_reparented(deadline_ms: u64, root_pid: i32) -> usize {
let me = unsafe { libc::getpid() } as u32;
let deadline = Instant::now() + Duration::from_millis(deadline_ms);
let mut killed = 0usize;
loop {
let candidates = scan_children(me, root_pid);
if candidates.is_empty() {
if root_settled(root_pid, me) {
return killed;
}
} else {
for pid in candidates {
if unsafe { libc::kill(pid, libc::SIGKILL) } == 0 {
killed += 1;
}
let mut status = 0i32;
unsafe { libc::waitpid(pid, &mut status, libc::WNOHANG) };
}
}
if Instant::now() >= deadline {
return killed;
}
std::thread::sleep(SWEEP_POLL);
}
}
fn scan_children(me: u32, exclude_pid: i32) -> Vec<i32> {
let mut children = Vec::new();
let Ok(entries) = std::fs::read_dir("/proc") else {
return children;
};
for entry in entries.flatten() {
let Ok(name) = entry.file_name().into_string() else {
continue;
};
let Ok(pid) = name.parse::<i32>() else {
continue;
};
if pid <= 0 || pid == exclude_pid {
continue;
}
let Ok(status) = std::fs::read_to_string(format!("/proc/{pid}/status")) else {
continue; };
if ppid_from_status(&status) == Some(me) {
children.push(pid);
}
}
children
}
fn root_settled(root_pid: i32, me: u32) -> bool {
let Ok(status) = std::fs::read_to_string(format!("/proc/{root_pid}/status")) else {
return true;
};
if ppid_from_status(&status) != Some(me) {
return true;
}
state_letter(&status) == Some('Z')
}
pub fn ppid_from_status(status_text: &str) -> Option<u32> {
for line in status_text.lines() {
let line = line.trim_start();
if let Some(rest) = line.strip_prefix("PPid:") {
return rest.trim().parse::<u32>().ok();
}
}
None
}
fn state_letter(status_text: &str) -> Option<char> {
for line in status_text.lines() {
let line = line.trim_start();
if let Some(rest) = line.strip_prefix("State:") {
return rest.trim_start().chars().next();
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ppid_is_parsed_from_proc_status_body() {
let status = "Name:\tsleep\nUmask:\t0022\nState:\tS (sleeping)\nTgid:\t4242\n\
Ngid:\t0\nPid:\t4242\nPPid:\t1337\nTracerPid:\t0\nUid:\t1000\t1000\t1000\t1000\n";
assert_eq!(ppid_from_status(status), Some(1337));
}
#[test]
fn ppid_of_pid_one_is_parsed() {
let status = "Name:\tsystemd\nState:\tS (sleeping)\nPPid:\t0\n";
assert_eq!(ppid_from_status(status), Some(0));
}
#[test]
fn missing_or_malformed_ppid_is_none() {
assert_eq!(ppid_from_status("Name:\tx\nState:\tR (running)\n"), None);
assert_eq!(ppid_from_status(""), None);
assert_eq!(ppid_from_status("PPid:\tnot-a-number\n"), None);
assert_eq!(ppid_from_status("PPidTracer:\t5\n"), None);
}
#[test]
fn indented_ppid_line_is_still_found() {
let status = "Name:\tsleep\n PPid: 99\n";
assert_eq!(ppid_from_status(status), Some(99));
}
#[test]
fn state_letter_reads_the_first_state_char() {
assert_eq!(state_letter("State:\tZ (zombie)\n"), Some('Z'));
assert_eq!(state_letter("State:\tS (sleeping)\n"), Some('S'));
assert_eq!(state_letter("Name:\tx\n"), None);
}
}