use super::*;
pub(crate) struct SignalHandler {
caught: Option<Signal>,
children: BTreeMap<i32, Command>,
initialized: bool,
verbosity: Verbosity,
}
impl SignalHandler {
pub(crate) fn install(verbosity: Verbosity) -> RunResult<'static> {
let mut instance = Self::instance();
instance.verbosity = verbosity;
if !instance.initialized {
Platform::install_signal_handler(|signal| Self::instance().handle(signal))?;
instance.initialized = true;
}
Ok(())
}
pub(crate) fn instance() -> MutexGuard<'static, Self> {
static INSTANCE: Mutex<SignalHandler> = Mutex::new(SignalHandler::new());
match INSTANCE.lock() {
Ok(guard) => guard,
Err(poison_error) => {
eprintln!(
"{}",
Error::internal(format!("signal handler mutex poisoned: {poison_error}"),)
.color_display(Color::auto().stderr())
);
process::exit(EXIT_FAILURE);
}
}
}
const fn new() -> Self {
Self {
caught: None,
children: BTreeMap::new(),
initialized: false,
verbosity: Verbosity::default(),
}
}
fn handle(&mut self, signal: Signal) {
if signal.is_fatal() {
if self.children.is_empty() {
process::exit(signal.code());
}
if self.caught.is_none() {
self.caught = Some(signal);
}
}
match signal {
Signal::Hangup | Signal::Interrupt | Signal::Quit => {}
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))]
Signal::Info => {
let id = process::id();
if self.children.is_empty() {
eprintln!("just {id}: no child processes");
} else {
let n = self.children.len();
let mut message = format!(
"just {id}: {n} child {}:\n",
if n == 1 { "process" } else { "processes" }
);
for (&child, command) in &self.children {
use std::fmt::Write;
writeln!(message, "{child}: {command:?}").unwrap();
}
eprint!("{message}");
}
}
Signal::Terminate =>
{
#[cfg(not(windows))]
for &child in self.children.keys() {
if self.verbosity.loquacious() {
eprintln!("just: sending SIGTERM to child process {child}");
}
nix::sys::signal::kill(
nix::unistd::Pid::from_raw(child),
Some(Signal::Terminate.into()),
)
.ok();
}
}
}
}
pub(crate) fn spawn<T>(
mut command: Command,
f: impl Fn(process::Child) -> io::Result<T>,
) -> (io::Result<T>, Option<Signal>) {
let mut instance = Self::instance();
let child = match command.spawn() {
Err(err) => return (Err(err), None),
Ok(child) => child,
};
let pid = match child.id().try_into() {
Err(err) => {
return (
Err(io::Error::other(format!("invalid child PID: {err}"))),
None,
);
}
Ok(pid) => pid,
};
instance.children.insert(pid, command);
drop(instance);
let result = f(child);
let mut instance = Self::instance();
instance.children.remove(&pid);
(result, instance.caught)
}
}