use std::io; use std::os::unix::process::ExitStatusExt; use std::process::ExitStatus; use std::sync::OnceLock; const SIGNALS: [(&str, libc::c_int); 16] = [ ("SIGHUP", libc::SIGHUP), ("SIGINT", libc::SIGINT), ("SIGQUIT", libc::SIGQUIT), ("SIGABRT", libc::SIGABRT), ("SIGKILL", libc::SIGKILL), ("SIGUSR1", libc::SIGUSR1), ("SIGUSR2", libc::SIGUSR2), ("SIGPIPE", libc::SIGPIPE), ("SIGALRM", libc::SIGALRM), ("SIGTERM", libc::SIGTERM), ("SIGCONT", libc::SIGCONT), ("SIGSTOP", libc::SIGSTOP), ("SIGTSTP", libc::SIGTSTP), ("SIGTTIN", libc::SIGTTIN), ("SIGTTOU", libc::SIGTTOU), ("SIGWINCH", libc::SIGWINCH), ]; pub(crate) fn signal_number(name: &str) -> io::Result { SIGNALS .iter() .find(|(text, _)| *text == name) .map(|(_, number)| *number) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Unsupported subprocess signal")) } /// Whether this names a real (if not necessarily listenable) signal. pub fn known_signal(name: &str) -> bool { SIGNALS.iter().any(|(text, _)| *text == name) } pub fn validate_signal(name: &str) -> io::Result<()> { signal_number(name).map(drop) } pub fn exit_signal(status: &ExitStatus) -> Option { let number = status.signal()?; Some( SIGNALS .iter() .find(|(_, value)| *value == number) .map_or_else(|| format!("SIG{number}"), |(name, _)| (*name).into()), ) } /// A listenable signal's inherited disposition, snapshotted once at /// process start. Every fate decision consults the snapshot instead of a /// live query, which later installs (supervisor, feeds, owners) would /// otherwise corrupt. #[derive(Clone, Copy, PartialEq)] pub enum Prior { Default, Ignored, Owned, } /// The signals whose startup disposition the snapshot records: every /// signal listener feeds can bind plus the supervisor's own two. const SNAPSHOT_SIGNALS: [libc::c_int; 6] = [ libc::SIGINT, libc::SIGTERM, libc::SIGHUP, libc::SIGUSR1, libc::SIGUSR2, libc::SIGWINCH, ]; static ORIGINALS: OnceLock<[(libc::c_int, Prior); 6]> = OnceLock::new(); /// Records each listenable signal's inherited disposition. Call once /// at process start, before the supervisor, feeds, or owners install /// anything; later installs must not move the baseline. pub fn snapshot_originals() { let _ = ORIGINALS.get_or_init(|| { SNAPSHOT_SIGNALS.map(|number| { let mut old: libc::sigaction = unsafe { std::mem::zeroed() }; unsafe { libc::sigaction(number, std::ptr::null(), &mut old); } ( number, if old.sa_sigaction == libc::SIG_IGN { Prior::Ignored } else if old.sa_sigaction == libc::SIG_DFL { Prior::Default } else { Prior::Owned }, ) }) }); } /// A listenable signal's startup disposition. Falls back to a live /// query when the process never snapshotted (in-process tests). pub fn original_prior(number: libc::c_int) -> Prior { if let Some(found) = ORIGINALS.get().and_then(|originals| { originals .iter() .find(|(signal, _)| *signal == number) .map(|(_, prior)| *prior) }) { return found; } let mut old: libc::sigaction = unsafe { std::mem::zeroed() }; unsafe { libc::sigaction(number, std::ptr::null(), &mut old); } if old.sa_sigaction == libc::SIG_IGN { Prior::Ignored } else if old.sa_sigaction == libc::SIG_DFL { Prior::Default } else { Prior::Owned } } /// Whether this signal was ignored at process start. Consult the /// snapshot, never the live disposition. pub fn originally_ignored(number: libc::c_int) -> bool { original_prior(number) == Prior::Ignored } pub(crate) fn send(pid: u32, group: bool, number: libc::c_int) -> std::io::Result { let target = if group { -(pid as libc::pid_t) } else { pid as libc::pid_t }; if unsafe { libc::kill(target, number) } == 0 { return Ok(true); } let error = std::io::Error::last_os_error(); if error.raw_os_error() == Some(libc::ESRCH) { Ok(false) } else { Err(error) } }