use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use std::thread;
use signal_hook::consts::{SIGINT, SIGTERM};
use signal_hook::iterator::Signals;
static INSTALLED: AtomicBool = AtomicBool::new(false);
static CHILD_PIDS: Mutex<Vec<u32>> = Mutex::new(Vec::new());
pub fn install() {
if INSTALLED.swap(true, Ordering::SeqCst) {
return;
}
let mut signals = match Signals::new([SIGINT, SIGTERM]) {
Ok(s) => s,
Err(_) => return, };
thread::spawn(move || {
for sig in signals.forever() {
super::screen::restore_terminal();
terminate_children();
std::process::exit(128 + sig);
}
});
}
pub fn isolate_child(cmd: &mut Command) {
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
cmd.process_group(0); }
#[cfg(not(unix))]
let _ = cmd;
}
pub fn register_child(pid: u32) {
if let Ok(mut v) = CHILD_PIDS.lock() {
v.push(pid);
}
}
pub fn unregister_child(pid: u32) {
if let Ok(mut v) = CHILD_PIDS.lock() {
v.retain(|&p| p != pid);
}
}
pub fn terminate_children() {
let pids = CHILD_PIDS.lock().map(|v| v.clone()).unwrap_or_default();
for pid in pids {
let _ = Command::new("kill").arg("-TERM").arg(pid.to_string()).status();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(target_os = "linux")]
#[test]
fn isolated_child_runs_in_its_own_process_group() {
fn pgid(stat_path: &str) -> i32 {
let stat = std::fs::read_to_string(stat_path).unwrap();
let after = &stat[stat.rfind(')').unwrap() + 1..];
after.split_whitespace().nth(2).unwrap().parse().unwrap() }
let ours = pgid("/proc/self/stat");
let mut cmd = Command::new("sleep");
cmd.arg("30");
isolate_child(&mut cmd);
let mut child = cmd.spawn().expect("spawn sleep");
let id = child.id();
let theirs = pgid(&format!("/proc/{id}/stat"));
let _ = child.kill();
let _ = child.wait();
assert_eq!(theirs, id as i32, "the child should lead its own process group");
assert_ne!(theirs, ours, "the child must not share scsh's process group");
}
#[test]
fn register_and_unregister_track_children() {
let fake = 4_242_424; register_child(fake);
assert!(CHILD_PIDS.lock().unwrap().contains(&fake));
unregister_child(fake);
assert!(!CHILD_PIDS.lock().unwrap().contains(&fake));
}
}