use crate::ported::signals::{queue_front, queue_rear, signal_mask_queue, signal_queue};
use std::sync::atomic::{AtomicI32, Ordering};
use crate::{DPUTS, DPUTS2};
use crate::signals::{queue_in, queueing_enabled};
#[cfg(target_os = "linux")]
pub const SIGCOUNT: i32 = 64;
#[cfg(target_os = "macos")]
pub const SIGCOUNT: i32 = 31;
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub const SIGCOUNT: i32 = 31;
pub const SIGZERR: i32 = SIGCOUNT + 1;
pub const SIGDEBUG: i32 = SIGCOUNT + 2;
pub const VSIGCOUNT: i32 = SIGCOUNT + 3;
#[cfg(target_os = "linux")]
pub const TRAPCOUNT: i32 = VSIGCOUNT + 32;
#[cfg(not(target_os = "linux"))]
pub const TRAPCOUNT: i32 = VSIGCOUNT;
pub const SIGEXIT: i32 = 0;
pub static SIGS: &[(&str, i32)] = &[
("HUP", libc::SIGHUP),
("INT", libc::SIGINT),
("QUIT", libc::SIGQUIT),
("ILL", libc::SIGILL),
("TRAP", libc::SIGTRAP),
("ABRT", libc::SIGABRT),
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "solaris",
))]
("EMT", libc::SIGEMT),
("BUS", libc::SIGBUS),
("FPE", libc::SIGFPE),
("KILL", libc::SIGKILL),
("USR1", libc::SIGUSR1),
("SEGV", libc::SIGSEGV),
("USR2", libc::SIGUSR2),
("PIPE", libc::SIGPIPE),
("ALRM", libc::SIGALRM),
("TERM", libc::SIGTERM),
("CHLD", libc::SIGCHLD),
("CONT", libc::SIGCONT),
("STOP", libc::SIGSTOP),
("TSTP", libc::SIGTSTP),
("TTIN", libc::SIGTTIN),
("TTOU", libc::SIGTTOU),
("URG", libc::SIGURG),
("XCPU", libc::SIGXCPU),
("XFSZ", libc::SIGXFSZ),
("VTALRM", libc::SIGVTALRM),
("PROF", libc::SIGPROF),
("WINCH", libc::SIGWINCH),
("IO", libc::SIGIO),
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
("INFO", libc::SIGINFO),
("SYS", libc::SIGSYS),
];
pub static ALT_SIGS: &[(&str, i32)] = &[
("CLD", libc::SIGCHLD), ("IOT", libc::SIGABRT), ("ERR", SIGZERR), ];
pub static SIG_MSG: &[(i32, &str)] = &[
(libc::SIGHUP, "hangup"),
(libc::SIGINT, "interrupt"),
(libc::SIGQUIT, "quit"),
(libc::SIGILL, "illegal hardware instruction"),
(libc::SIGTRAP, "trace trap"),
(libc::SIGABRT, "abort"),
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "solaris",
))]
(libc::SIGEMT, "EMT trap"),
(libc::SIGBUS, "bus error"),
(libc::SIGFPE, "floating point exception"),
(libc::SIGKILL, "killed"),
(libc::SIGUSR1, "user-defined signal 1"),
(libc::SIGSEGV, "segmentation fault"),
(libc::SIGUSR2, "user-defined signal 2"),
(libc::SIGPIPE, "broken pipe"),
(libc::SIGALRM, "alarm"),
(libc::SIGTERM, "terminated"),
(libc::SIGCHLD, "death of child"),
(libc::SIGCONT, "continued"),
(libc::SIGSTOP, "stopped (signal)"),
(libc::SIGTSTP, "stopped"),
(libc::SIGTTIN, "stopped (tty input)"),
(libc::SIGTTOU, "stopped (tty output)"),
(libc::SIGURG, "urgent condition"),
(libc::SIGXCPU, "cpu limit exceeded"),
(libc::SIGXFSZ, "file size limit exceeded"),
(libc::SIGVTALRM, "virtual time alarm"),
(libc::SIGPROF, "profile signal"),
(libc::SIGWINCH, "window size changed"),
(libc::SIGIO, "i/o ready"),
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
))]
(libc::SIGINFO, "information request"),
(libc::SIGSYS, "invalid system call"),
];
pub fn sigs_name(idx: i32) -> Option<&'static str> {
if idx == 0 {
return Some("EXIT");
} if idx == SIGZERR {
return Some("ZERR");
} if idx == SIGDEBUG {
return Some("DEBUG");
} SIGS.iter().find(|(_, n)| *n == idx).map(|(name, _)| *name)
}
pub fn sigs_number(name: &str) -> Option<i32> {
let bare = name.strip_prefix("SIG").unwrap_or(name);
SIGS.iter()
.find(|(n, _)| *n == bare)
.map(|(_, num)| *num)
.or_else(|| {
ALT_SIGS
.iter()
.find(|(n, _)| *n == bare)
.map(|(_, num)| *num)
})
}
#[inline]
#[allow(non_snake_case)]
pub fn SIGNUM(x: i32) -> i32 {
#[cfg(target_os = "linux")]
{
if x >= VSIGCOUNT {
x - VSIGCOUNT + libc::SIGRTMIN()
} else {
x
}
}
#[cfg(not(target_os = "linux"))]
{
x
}
}
#[inline]
#[allow(non_snake_case)]
pub fn SIGIDX(x: i32) -> i32 {
#[cfg(target_os = "linux")]
{
let rtmin = libc::SIGRTMIN();
let rtmax = libc::SIGRTMAX();
if x >= rtmin && x <= rtmax {
x - rtmin + VSIGCOUNT
} else {
x
}
}
#[cfg(not(target_os = "linux"))]
{
x
}
}
pub const MAX_QUEUE_SIZE: usize = 128;
#[inline]
#[allow(non_snake_case)]
pub fn queue_signals() {
queue_in.fetch_add(1, Ordering::SeqCst);
queueing_enabled.fetch_add(1, Ordering::SeqCst);
}
#[inline]
#[allow(non_snake_case)]
pub fn unqueue_signals() {
DPUTS!(
queueing_enabled.load(Ordering::SeqCst) == 0, "BUG: unqueue_signals called but not queueing" );
queue_in.fetch_sub(1, Ordering::SeqCst); let prev = queueing_enabled.fetch_sub(1, Ordering::SeqCst); if prev == 1 {
run_queued_signals(); }
}
#[inline]
#[allow(non_snake_case)]
pub fn dont_queue_signals() {
let level = queueing_enabled.swap(0, Ordering::SeqCst);
queue_in.store(level, Ordering::SeqCst);
run_queued_signals();
}
#[inline]
#[allow(non_snake_case)]
pub fn restore_queue_signals(q: i32) {
let qi = queue_in.load(Ordering::SeqCst); let qe = queueing_enabled.load(Ordering::SeqCst); DPUTS2!(
qe != 0 && qi != q, "BUG: q = {} != queue_in = {}",
q,
qi );
queue_in.store(q, Ordering::SeqCst); queueing_enabled.store(q, Ordering::SeqCst); }
#[inline]
#[allow(non_snake_case)]
pub fn queue_signal_level() -> i32 {
queueing_enabled.load(Ordering::SeqCst)
}
#[inline]
#[allow(non_snake_case)]
pub fn run_queued_signals() {
loop {
let f = queue_front.load(Ordering::SeqCst);
let r = queue_rear.load(Ordering::SeqCst);
if f == r {
break;
}
let nf = (f + 1) % MAX_QUEUE_SIZE;
let sig = signal_queue[nf].load(Ordering::SeqCst);
let mask = signal_mask_queue
.lock()
.ok()
.and_then(|g| g.get(nf).copied());
queue_front.store(nf, Ordering::SeqCst);
if let Some(m) = mask {
let _ = crate::ported::signals::signal_setmask(&m);
}
unsafe {
libc::raise(sig);
}
}
}
#[inline]
#[allow(non_snake_case)]
#[cfg(unix)]
pub fn child_block() {
let mask = crate::ported::signals::signal_mask(libc::SIGCHLD);
let _ = crate::ported::signals::signal_block(&mask);
}
#[inline]
#[allow(non_snake_case)]
#[cfg(not(unix))]
pub fn child_block() {}
#[inline]
#[allow(non_snake_case)]
#[cfg(unix)]
pub fn child_unblock() {
let mask = crate::ported::signals::signal_mask(libc::SIGCHLD);
let _ = crate::ported::signals::signal_unblock(&mask);
}
#[inline]
#[allow(non_snake_case)]
#[cfg(not(unix))]
pub fn child_unblock() {}
#[inline]
#[allow(non_snake_case)]
#[cfg(unix)]
pub fn winch_block() {
let mask = crate::ported::signals::signal_mask(libc::SIGWINCH);
let _ = crate::ported::signals::signal_block(&mask);
}
#[inline]
#[allow(non_snake_case)]
#[cfg(not(unix))]
pub fn winch_block() {}
#[inline]
#[allow(non_snake_case)]
#[cfg(unix)]
pub fn winch_unblock() {
let mask = crate::ported::signals::signal_mask(libc::SIGWINCH);
let _ = crate::ported::signals::signal_unblock(&mask);
}
#[inline]
#[allow(non_snake_case)]
#[cfg(not(unix))]
pub fn winch_unblock() {}
#[inline]
#[allow(non_snake_case)]
#[cfg(unix)]
pub fn signal_ignore(s: i32) -> libc::sighandler_t {
unsafe { libc::signal(s, libc::SIG_IGN) }
}
#[inline]
#[allow(non_snake_case)]
#[cfg(not(unix))]
pub fn signal_ignore(_s: i32) -> usize {
0
}
#[inline]
#[allow(non_snake_case)]
#[cfg(unix)]
pub fn signal_default(s: i32) -> libc::sighandler_t {
unsafe { libc::signal(s, libc::SIG_DFL) }
}
#[inline]
#[allow(non_snake_case)]
#[cfg(not(unix))]
pub fn signal_default(_s: i32) -> usize {
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pseudo_signal_indexes_correct() {
let _g = crate::test_util::global_state_lock();
assert_eq!(SIGEXIT, 0);
assert_eq!(SIGZERR, SIGCOUNT + 1);
assert_eq!(SIGDEBUG, SIGCOUNT + 2);
assert_eq!(VSIGCOUNT, SIGCOUNT + 3);
assert!(VSIGCOUNT > 0);
}
#[test]
fn trapcount_at_least_vsigcount() {
let _g = crate::test_util::global_state_lock();
assert!(TRAPCOUNT >= VSIGCOUNT);
}
#[test]
fn signum_sigidx_round_trip_for_normal_signal() {
let _g = crate::test_util::global_state_lock();
assert_eq!(SIGNUM(libc::SIGINT), libc::SIGINT);
assert_eq!(SIGIDX(libc::SIGINT), libc::SIGINT);
}
#[test]
fn max_queue_size_is_128() {
let _g = crate::test_util::global_state_lock();
assert_eq!(MAX_QUEUE_SIZE, 128);
}
#[test]
fn queue_unqueue_balance() {
let _g = crate::test_util::global_state_lock();
let initial = queue_signal_level();
queue_signals();
assert_eq!(queue_signal_level(), initial + 1);
queue_signals();
assert_eq!(queue_signal_level(), initial + 2);
unqueue_signals();
assert_eq!(queue_signal_level(), initial + 1);
unqueue_signals();
assert_eq!(queue_signal_level(), initial);
}
#[test]
fn dont_queue_signals_zeros_counter() {
let _g = crate::test_util::global_state_lock();
queue_signals();
queue_signals();
queue_signals();
dont_queue_signals();
assert_eq!(queue_signal_level(), 0);
}
#[test]
fn restore_queue_signals_sets_counter() {
let _g = crate::test_util::global_state_lock();
restore_queue_signals(0);
restore_queue_signals(5);
assert_eq!(queue_signal_level(), 5);
restore_queue_signals(0);
}
#[cfg(unix)]
#[test]
fn signal_ignore_returns_previous_handler() {
let _g = crate::test_util::global_state_lock();
let _ = signal_default(libc::SIGUSR2);
let prev = signal_ignore(libc::SIGUSR2);
assert_eq!(
prev,
libc::SIG_DFL,
"c:64 — first signal_ignore must return prior SIG_DFL"
);
let prev2 = signal_ignore(libc::SIGUSR2);
assert_eq!(
prev2,
libc::SIG_IGN,
"c:64 — second signal_ignore must return prior SIG_IGN"
);
let _ = signal_default(libc::SIGUSR2);
}
#[cfg(unix)]
#[test]
fn signal_default_returns_previous_handler() {
let _g = crate::test_util::global_state_lock();
let _ = signal_default(libc::SIGUSR2);
let _ = signal_ignore(libc::SIGUSR2);
let prev = signal_default(libc::SIGUSR2);
assert_eq!(
prev,
libc::SIG_IGN,
"c:67 — signal_default must return prior SIG_IGN"
);
let prev2 = signal_default(libc::SIGUSR2);
assert_eq!(prev2, libc::SIG_DFL);
}
#[cfg(unix)]
#[test]
fn signum_sigidx_round_trip_full_range() {
let _g = crate::test_util::global_state_lock();
for s in 1..VSIGCOUNT {
let n = SIGNUM(s);
let back = SIGIDX(n);
assert_eq!(
back, s,
"round-trip failed at signal {}: SIGIDX(SIGNUM({})) = {}",
s, s, back
);
}
}
#[cfg(unix)]
#[test]
fn sigs_name_pseudo_signal_exit() {
let _g = crate::test_util::global_state_lock();
let n = sigs_name(SIGEXIT);
assert!(n.is_some(), "SIGEXIT (index 0) must have a name");
let s = n.unwrap();
assert!(
!s.is_empty(),
"SIGEXIT name must be non-empty (got {:?})",
s
);
}
#[cfg(unix)]
#[test]
fn sigs_name_sigint_resolves() {
let _g = crate::test_util::global_state_lock();
let n = sigs_name(libc::SIGINT);
let alt = sigs_name(SIGIDX(libc::SIGINT));
assert!(
n.is_some() || alt.is_some(),
"SIGINT must resolve to a name via direct or SIGIDX path"
);
}
#[test]
fn sigs_name_out_of_range_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(sigs_name(-1).is_none());
assert!(sigs_name(99999).is_none());
assert!(sigs_name(TRAPCOUNT + 100).is_none());
}
#[cfg(unix)]
#[test]
fn sigs_number_resolves_canonical_short_name() {
let _g = crate::test_util::global_state_lock();
assert!(
sigs_number("INT").is_some(),
"sigs_number(INT) must resolve"
);
assert!(sigs_number("TERM").is_some());
assert!(sigs_number("HUP").is_some());
assert!(sigs_number("KILL").is_some());
}
#[test]
fn sigs_number_unknown_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(sigs_number("ZZBOGUSXX").is_none());
assert!(sigs_number("").is_none());
assert!(sigs_number("not_a_signal").is_none());
}
#[test]
fn run_queued_signals_with_empty_queue_is_safe() {
let _g = crate::test_util::global_state_lock();
dont_queue_signals();
run_queued_signals();
run_queued_signals(); }
#[test]
fn restore_queue_signals_negative_value_is_safe() {
let _g = crate::test_util::global_state_lock();
let saved = queue_signal_level();
restore_queue_signals(-1);
let _ = queue_signal_level();
restore_queue_signals(saved.max(0));
}
#[test]
fn pseudo_signals_above_libc_signal_range() {
let _g = crate::test_util::global_state_lock();
assert!(
SIGZERR > SIGCOUNT,
"SIGZERR ({}) must be > SIGCOUNT ({}) to avoid libc collision",
SIGZERR,
SIGCOUNT
);
assert!(
SIGDEBUG > SIGCOUNT,
"SIGDEBUG ({}) must be > SIGCOUNT ({})",
SIGDEBUG,
SIGCOUNT
);
assert_ne!(SIGZERR, SIGDEBUG);
assert_ne!(SIGZERR, SIGEXIT);
assert_ne!(SIGDEBUG, SIGEXIT);
}
#[test]
fn signals_corpus_sigs_number_int_returns_sigint() {
let n = sigs_number("INT");
assert_eq!(n, Some(libc::SIGINT), "INT → SIGINT");
}
#[test]
fn signals_corpus_sigs_number_term_returns_sigterm() {
let n = sigs_number("TERM");
assert_eq!(n, Some(libc::SIGTERM), "TERM → SIGTERM");
}
#[test]
fn signals_corpus_sigs_number_hup_returns_sighup() {
let n = sigs_number("HUP");
assert_eq!(n, Some(libc::SIGHUP), "HUP → SIGHUP");
}
#[test]
fn signals_corpus_sigs_number_kill_returns_sigkill() {
let n = sigs_number("KILL");
assert_eq!(n, Some(libc::SIGKILL), "KILL → SIGKILL");
}
#[test]
fn signals_corpus_sigs_number_unknown_returns_none() {
assert!(sigs_number("BOGUS_NEVER").is_none());
assert!(sigs_number("").is_none());
}
#[test]
fn signals_corpus_sigs_name_int_returns_short_form() {
let n = sigs_name(libc::SIGINT);
assert!(n.is_some(), "SIGINT must have a name");
assert_eq!(n.unwrap(), "INT", "short form, not 'SIGINT'");
}
#[test]
fn signals_corpus_sigs_name_term_returns_short_form() {
assert_eq!(sigs_name(libc::SIGTERM), Some("TERM"));
}
#[test]
fn signals_corpus_sigs_name_zero_does_not_panic() {
let _ = sigs_name(0);
}
#[test]
fn signals_corpus_round_trip_int() {
let n = sigs_number("INT").expect("INT exists");
let back = sigs_name(n).expect("name for SIGINT");
assert_eq!(back, "INT", "round-trip preserves canonical name");
}
#[test]
fn signals_corpus_distinct_numbers() {
let i = sigs_number("INT").unwrap();
let t = sigs_number("TERM").unwrap();
let h = sigs_number("HUP").unwrap();
let k = sigs_number("KILL").unwrap();
let q = sigs_number("QUIT").unwrap();
assert_ne!(i, t);
assert_ne!(i, h);
assert_ne!(t, h);
assert_ne!(k, q);
}
}