use std::sync::atomic::{AtomicUsize, Ordering};
use super::*;
const MAX_TRAP_SIG: usize = 128;
static PENDING_TRAPS: [AtomicUsize; MAX_TRAP_SIG] = [const { AtomicUsize::new(0) }; MAX_TRAP_SIG];
pub(super) extern "C" fn trap_signal_handler(sig: i32) {
if sig > 0 && (sig as usize) < MAX_TRAP_SIG {
PENDING_TRAPS[sig as usize].fetch_add(1, Ordering::Relaxed);
}
}
pub(super) fn clear_pending_trap(sig: i32) {
if sig > 0 && (sig as usize) < MAX_TRAP_SIG {
PENDING_TRAPS[sig as usize].store(0, Ordering::Relaxed);
}
}
#[cfg(any(
feature = "frontend",
all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(crate) fn clear_all_pending_traps() {
for pending in PENDING_TRAPS.iter().skip(1) {
pending.store(0, Ordering::Relaxed);
}
}
pub(super) fn run_pending_traps<R: Runtime>(state: &mut ShellState, runtime: &mut R) {
let last_status = state.last_status;
for (sig, pending_slot) in PENDING_TRAPS.iter().enumerate().skip(1) {
let Some(action) = state.traps.get(&(sig as i32)).cloned() else {
continue;
};
let pending = pending_slot.swap(0, Ordering::Relaxed);
if pending == 0 {
continue;
}
if action.is_empty() {
continue;
}
for _ in 0..pending {
let _ = run_string(state, runtime, &action);
}
state.set_last_status(last_status);
}
}
#[cfg(any(
feature = "frontend",
all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(super) fn install_restored_traps(state: &mut ShellState) {
if !state.manage_signals {
return;
}
for (sig, action) in state.traps.clone() {
if sig <= 0 {
continue;
}
if action.is_empty() {
state
.trap_signals
.handlers
.entry(sig)
.or_insert_with(|| unsafe { libc::signal(sig, libc::SIG_IGN) });
continue;
}
state
.trap_signals
.handlers
.entry(sig)
.or_insert_with(|| unsafe {
libc::signal(sig, trap_signal_handler as *const () as libc::sighandler_t)
});
unsafe {
libc::signal(sig, trap_signal_handler as *const () as libc::sighandler_t);
}
}
}
pub(super) fn builtin_trap(state: &mut ShellState, args: &[String]) -> i32 {
if args.is_empty() {
let mut entries: Vec<(i32, String)> = state
.traps
.iter()
.map(|(sig, action)| (*sig, action.clone()))
.collect();
entries.sort_by_key(|(sig, _)| *sig);
for (sig, action) in entries {
shell_outln(
state,
&format!(
"trap -- '{}' {}",
action.replace('\'', "'\\''"),
trap_signal_name(sig)
),
);
}
return 0;
}
if args.len() == 1 {
let sig = match parse_trap_signal(&args[0]) {
Some(sig) => sig,
None => {
shell_errln(
state,
&format!("trap: {}: invalid signal specification", args[0]),
);
return 1;
}
};
if let Some(action) = state.traps.get(&sig) {
shell_outln(
state,
&format!(
"trap -- '{}' {}",
action.replace('\'', "'\\''"),
trap_signal_name(sig)
),
);
}
return 0;
}
let action = &args[0];
for sig_name in &args[1..] {
let sig = match parse_trap_signal(sig_name) {
Some(sig) => sig,
None => {
shell_errln(
state,
&format!("trap: {sig_name}: invalid signal specification"),
);
return 1;
}
};
if sig == libc::SIGKILL || sig == libc::SIGSTOP {
shell_errln(
state,
"trap: setting a trap for SIGKILL or SIGSTOP produces undefined results",
);
return 1;
}
if action == "-" {
state.traps.remove(&sig);
clear_pending_trap(sig);
if sig > 0 && state.manage_signals {
let handler = state
.trap_signals
.handlers
.remove(&sig)
.unwrap_or(libc::SIG_DFL);
unsafe { libc::signal(sig, handler) };
}
} else if action.is_empty() {
state.traps.insert(sig, action.clone());
clear_pending_trap(sig);
if sig > 0 && state.manage_signals {
state
.trap_signals
.handlers
.entry(sig)
.or_insert_with(|| unsafe { libc::signal(sig, libc::SIG_IGN) });
}
} else {
state.traps.insert(sig, action.clone());
clear_pending_trap(sig);
if sig > 0 && state.manage_signals {
state
.trap_signals
.handlers
.entry(sig)
.or_insert_with(|| unsafe {
libc::signal(sig, trap_signal_handler as *const () as libc::sighandler_t)
});
unsafe {
libc::signal(sig, trap_signal_handler as *const () as libc::sighandler_t)
};
}
}
}
0
}
pub(super) fn parse_trap_signal(sig: &str) -> Option<i32> {
if sig == "0" || sig == "EXIT" {
return Some(TRAP_EXIT);
}
if let Ok(num) = sig.parse::<i32>() {
return Some(num);
}
let upper = sig.strip_prefix("SIG").unwrap_or(sig).to_ascii_uppercase();
match upper.as_str() {
"HUP" => Some(libc::SIGHUP),
"INT" => Some(libc::SIGINT),
"QUIT" => Some(libc::SIGQUIT),
"ABRT" => Some(libc::SIGABRT),
"ALRM" => Some(libc::SIGALRM),
"TERM" => Some(libc::SIGTERM),
"CHLD" => Some(libc::SIGCHLD),
"CONT" => Some(libc::SIGCONT),
"FPE" => Some(libc::SIGFPE),
"ILL" => Some(libc::SIGILL),
"KILL" => Some(libc::SIGKILL),
"PIPE" => Some(libc::SIGPIPE),
"SEGV" => Some(libc::SIGSEGV),
"STOP" => Some(libc::SIGSTOP),
"TSTP" => Some(libc::SIGTSTP),
"TTIN" => Some(libc::SIGTTIN),
"TTOU" => Some(libc::SIGTTOU),
"USR1" => Some(libc::SIGUSR1),
"USR2" => Some(libc::SIGUSR2),
"TRAP" => Some(libc::SIGTRAP),
"URG" => Some(libc::SIGURG),
"XCPU" => Some(libc::SIGXCPU),
"XFSZ" => Some(libc::SIGXFSZ),
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
"BUS" => Some(libc::SIGBUS),
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
"PROF" => Some(libc::SIGPROF),
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
"SYS" => Some(libc::SIGSYS),
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
"VTALRM" => Some(libc::SIGVTALRM),
_ => None,
}
}
pub(super) fn trap_signal_name(sig: i32) -> &'static str {
match sig {
TRAP_EXIT => "EXIT",
libc::SIGABRT => "ABRT",
libc::SIGALRM => "ALRM",
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
libc::SIGBUS => "BUS",
libc::SIGCHLD => "CHLD",
libc::SIGCONT => "CONT",
libc::SIGFPE => "FPE",
libc::SIGHUP => "HUP",
libc::SIGILL => "ILL",
libc::SIGINT => "INT",
libc::SIGKILL => "KILL",
libc::SIGPIPE => "PIPE",
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
libc::SIGPROF => "PROF",
libc::SIGQUIT => "QUIT",
libc::SIGSEGV => "SEGV",
libc::SIGSTOP => "STOP",
libc::SIGTERM => "TERM",
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
libc::SIGSYS => "SYS",
libc::SIGTRAP => "TRAP",
libc::SIGTSTP => "TSTP",
libc::SIGTTIN => "TTIN",
libc::SIGTTOU => "TTOU",
libc::SIGURG => "URG",
libc::SIGUSR1 => "USR1",
libc::SIGUSR2 => "USR2",
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
libc::SIGVTALRM => "VTALRM",
libc::SIGXCPU => "XCPU",
libc::SIGXFSZ => "XFSZ",
_ => "?",
}
}
#[cfg(any(
feature = "frontend",
all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(super) fn run_exit_trap<R: Runtime>(state: &mut ShellState, runtime: &mut R) {
let Some(action) = state.traps.remove(&TRAP_EXIT) else {
return;
};
let _ = run_string(state, runtime, &action);
}
#[cfg(any(
feature = "frontend",
all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(super) fn restore_trap_signals(state: &mut ShellState) {
if !state.manage_signals {
return;
}
for (sig, handler) in std::mem::take(&mut state.trap_signals.handlers) {
if sig > 0 {
unsafe { libc::signal(sig, handler) };
}
}
clear_all_pending_traps();
}