pub const SIGNAL_EXIT_BASE: i32 = 128;
pub const SIGINT: i32 = 2;
pub const SIGPIPE: i32 = 13;
pub const SIGTERM: i32 = 15;
pub fn exit_status_for_signal(signal: i32) -> i32 {
SIGNAL_EXIT_BASE + signal
}
pub fn child_exit_status(code: Option<i32>, signal: Option<i32>) -> i32 {
match (code, signal) {
(Some(code), _) => code,
(None, Some(signal)) => exit_status_for_signal(signal),
(None, None) => 1,
}
}
#[cfg(unix)]
pub fn restore_default_dispositions() -> std::io::Result<()> {
let previous = unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
if previous == libc::SIG_ERR {
return Err(std::io::Error::last_os_error());
}
unsafe {
let mut empty: libc::sigset_t = std::mem::zeroed();
if libc::sigemptyset(&raw mut empty) != 0 {
return Err(std::io::Error::last_os_error());
}
if libc::sigprocmask(libc::SIG_SETMASK, &raw const empty, std::ptr::null_mut()) != 0 {
return Err(std::io::Error::last_os_error());
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn signal_exit_statuses_match_the_shell_convention() {
assert_eq!(exit_status_for_signal(SIGINT), 130);
assert_eq!(exit_status_for_signal(SIGTERM), 143);
assert_eq!(exit_status_for_signal(SIGPIPE), 141);
}
#[test]
fn a_normal_exit_code_passes_through_unchanged() {
for code in [0, 1, 2, 42, 255] {
assert_eq!(child_exit_status(Some(code), None), code);
}
}
#[test]
fn a_signalled_child_reports_the_shell_status() {
assert_eq!(child_exit_status(None, Some(SIGTERM)), 143);
assert_eq!(child_exit_status(None, Some(SIGINT)), 130);
}
#[test]
fn an_exit_code_wins_over_a_signal() {
assert_eq!(child_exit_status(Some(3), Some(SIGTERM)), 3);
}
#[test]
fn an_unknown_outcome_is_a_generic_failure_not_a_success() {
assert_eq!(child_exit_status(None, None), 1);
}
#[cfg(unix)]
#[test]
fn the_signal_numbers_match_the_platforms() {
assert_eq!(SIGINT, libc::SIGINT);
assert_eq!(SIGPIPE, libc::SIGPIPE);
assert_eq!(SIGTERM, libc::SIGTERM);
}
#[cfg(unix)]
#[test]
fn restoring_dispositions_succeeds_and_actually_changes_sigpipe() {
let original = unsafe { libc::signal(libc::SIGPIPE, libc::SIG_IGN) };
assert_ne!(original, libc::SIG_ERR);
restore_default_dispositions().expect("restoring dispositions must succeed");
let now = unsafe { libc::signal(libc::SIGPIPE, original) };
assert_eq!(now, libc::SIG_DFL, "SIGPIPE was not restored to its default disposition");
}
#[cfg(unix)]
#[test]
fn restoring_dispositions_clears_the_signal_mask() {
let still_blocked = unsafe {
let mut blocked: libc::sigset_t = std::mem::zeroed();
libc::sigemptyset(&raw mut blocked);
libc::sigaddset(&raw mut blocked, libc::SIGUSR1);
libc::sigprocmask(libc::SIG_BLOCK, &raw const blocked, std::ptr::null_mut());
restore_default_dispositions().expect("restoring dispositions must succeed");
let mut current: libc::sigset_t = std::mem::zeroed();
libc::sigprocmask(libc::SIG_SETMASK, std::ptr::null(), &raw mut current);
libc::sigismember(&raw const current, libc::SIGUSR1)
};
assert_eq!(still_blocked, 0, "the signal mask was not cleared");
}
}