use nix::sys::signal::{sigprocmask, SigmaskHow};
use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal as NixSignal};
use nix::unistd::getpid;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::signals_h::{MAX_QUEUE_SIZE, SIGCOUNT, SIGDEBUG, SIGEXIT, SIGZERR, TRAPCOUNT, signal_default, signal_ignore};
use crate::zsh_h::{
isset, ERRFLAG_INT, INTERACTIVE, POSIXTRAPS, PRIVILEGED,
TRAP_STATE_FORCE_RETURN, TRAP_STATE_PRIMED, ZEXIT_SIGNAL,
ZSIG_FUNC, ZSIG_IGNORED, ZSIG_SHIFT, ZSIG_TRAPPED,
};
pub use crate::ported::jobs::{getsigidx, getsigname};
pub static queueing_enabled: AtomicI32 = AtomicI32::new(0);
pub static queue_front: AtomicUsize = AtomicUsize::new(0);
pub static queue_rear: AtomicUsize = AtomicUsize::new(0);
pub static queue_in: AtomicI32 = AtomicI32::new(0);
#[allow(clippy::declare_interior_mutable_const)]
const ATOM_I32_ZERO: AtomicI32 = AtomicI32::new(0);
pub static signal_queue: [AtomicI32; MAX_QUEUE_SIZE] = [ATOM_I32_ZERO; MAX_QUEUE_SIZE];
pub static signal_mask_queue: std::sync::LazyLock<Mutex<Vec<libc::sigset_t>>> = std::sync::LazyLock::new(|| {
let zero: libc::sigset_t = unsafe { std::mem::zeroed() };
Mutex::new(vec![zero; MAX_QUEUE_SIZE])
});
pub static trap_queueing_enabled: AtomicI32 = AtomicI32::new(0);
pub static trap_queue_front: AtomicUsize = AtomicUsize::new(0);
pub static trap_queue_rear: AtomicUsize = AtomicUsize::new(0);
pub static trap_queue: [AtomicI32; MAX_QUEUE_SIZE] = [ATOM_I32_ZERO; MAX_QUEUE_SIZE];
pub static last_signal: AtomicI32 = AtomicI32::new(0);
pub static sigtrapped: std::sync::LazyLock<Mutex<Vec<i32>>> = std::sync::LazyLock::new(|| Mutex::new(vec![0; TRAPCOUNT as usize]));
pub static siglists: std::sync::LazyLock<Mutex<Vec<Option<crate::ported::zsh_h::Eprog>>>> = std::sync::LazyLock::new(|| Mutex::new((0..TRAPCOUNT as usize).map(|_| None).collect()));
pub static nsigtrapped: AtomicI32 = AtomicI32::new(0);
pub static intrap: AtomicI32 = AtomicI32::new(0);
pub static in_exit_trap: AtomicI32 = AtomicI32::new(0);
pub static trapisfunc: AtomicI32 = AtomicI32::new(0);
pub static traplocallevel: AtomicI32 = AtomicI32::new(0);
pub use crate::ported::signals_h::{queue_signals, unqueue_signals};
pub fn queue_traps(_wait_cmd: i32) { trap_queueing_enabled.fetch_add(1, Ordering::SeqCst);
}
pub fn unqueue_traps() { trap_queueing_enabled.store(0, Ordering::SeqCst);
loop {
let f = trap_queue_front.load(Ordering::SeqCst);
let r = trap_queue_rear.load(Ordering::SeqCst);
if f == r { break; }
let nf = (f + 1) % MAX_QUEUE_SIZE;
let sig = trap_queue[nf].load(Ordering::SeqCst);
trap_queue_front.store(nf, Ordering::SeqCst);
let _ = handletrap(sig);
}
}
#[cfg(unix)]
pub fn signal_block(set: &libc::sigset_t) -> libc::sigset_t { let mut oset: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe {
libc::sigprocmask(libc::SIG_BLOCK, set, &mut oset);
}
oset
}
#[cfg(unix)]
pub fn signal_unblock(set: &libc::sigset_t) -> libc::sigset_t { let mut oset: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe {
libc::sigprocmask(libc::SIG_UNBLOCK, set, &mut oset);
}
oset
}
pub fn killpg(pgrp: i32, sig: i32) -> i32 {
unsafe { libc::killpg(pgrp, sig) }
}
pub fn kill(pid: i32, sig: i32) -> i32 {
unsafe { libc::kill(pid, sig) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sig_by_name() {
assert_eq!(getsigidx("INT"), Some(libc::SIGINT));
assert_eq!(getsigidx("SIGINT"), Some(libc::SIGINT));
assert_eq!(getsigidx("int"), Some(libc::SIGINT));
assert_eq!(getsigidx("HUP"), Some(libc::SIGHUP));
assert_eq!(getsigidx("TERM"), Some(libc::SIGTERM));
assert_eq!(getsigidx("EXIT"), Some(SIGEXIT));
assert_eq!(getsigidx("9"), Some(9));
}
#[test]
fn test_getsigname() {
assert_eq!(getsigname(libc::SIGINT), "INT");
assert_eq!(getsigname(libc::SIGHUP), "HUP");
assert_eq!(getsigname(SIGEXIT), "EXIT");
}
#[test]
fn test_signal_queue() {
let before = queueing_enabled.load(Ordering::SeqCst);
queue_signals();
assert_eq!(queueing_enabled.load(Ordering::SeqCst), before + 1);
unqueue_signals();
assert_eq!(queueing_enabled.load(Ordering::SeqCst), before);
}
#[test]
fn test_signal_mask_zero_returns_empty() {
let s = signal_mask(0);
let r = unsafe { libc::sigismember(&s, libc::SIGINT) };
assert_eq!(r, 0);
}
#[test]
fn test_signal_mask_includes_only_specified() {
let s = signal_mask(libc::SIGUSR1);
assert_eq!(unsafe { libc::sigismember(&s, libc::SIGUSR1) }, 1);
assert_eq!(unsafe { libc::sigismember(&s, libc::SIGUSR2) }, 0);
}
#[test]
fn test_interact_flag_round_trip() {
let prev = is_interact();
set_interact(true);
assert!(is_interact());
set_interact(false);
assert!(!is_interact());
set_interact(prev);
}
#[test]
fn test_signal_block_returns_old_mask() {
let prev = is_interact();
set_interact(false); let mask = signal_mask(libc::SIGUSR2);
let old = signal_block(&mask);
let _ = signal_setmask(&old);
let _ = old;
set_interact(prev);
}
}
fn interact_lock() -> &'static std::sync::atomic::AtomicBool {
static INTERACT: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
&INTERACT
}
pub fn set_interact(v: bool) {
interact_lock().store(v, std::sync::atomic::Ordering::SeqCst);
}
pub fn is_interact() -> bool {
interact_lock().load(std::sync::atomic::Ordering::SeqCst)
}
#[cfg(unix)]
pub fn install_handler(sig: i32) { unsafe {
let mut act: libc::sigaction = std::mem::zeroed();
act.sa_sigaction = zhandler as *const () as usize;
libc::sigemptyset(&mut act.sa_mask);
act.sa_flags = 0;
libc::sigaction(sig, &act, std::ptr::null_mut());
}
}
pub fn intr() { if is_interact() {
install_handler(libc::SIGINT);
}
}
pub fn endtrapscope() { let locallevel = crate::ported::utils::locallevel();
let exit_flags = sigtrapped.lock()
.ok()
.and_then(|g| g.get(SIGEXIT as usize).copied())
.unwrap_or(0);
let mut exittr: i32 = 0;
if intrap.load(Ordering::Relaxed) == 0 && !EXIT_TRAP_POSIX.load(Ordering::Relaxed) && exit_flags != 0
{
exittr = exit_flags;
if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(SIGEXIT as usize) { *slot = 0; }
}
if let Ok(mut g) = siglists.lock() {
if let Some(slot) = g.get_mut(SIGEXIT as usize) { *slot = None; }
}
if exit_flags & ZSIG_TRAPPED != 0 {
nsigtrapped.fetch_sub(1, Ordering::Relaxed); }
}
if let Ok(mut traps) = SAVETRAPS.get_or_init(|| Mutex::new(Vec::new())).lock() {
while let Some(st) = traps.first() { if st.local <= locallevel as i32 { break; } let st = traps.remove(0);
if st.flags != 0 || st.list.is_some() { DONTSAVETRAP.fetch_add(1, Ordering::Relaxed);
let _ = settrap(st.sig, st.list, st.flags); if st.sig == SIGEXIT {
EXIT_TRAP_POSIX.store(st.posix != 0, Ordering::Relaxed); }
DONTSAVETRAP.fetch_sub(1, Ordering::Relaxed); } else { if st.sig != SIGEXIT || !EXIT_TRAP_POSIX.load(Ordering::Relaxed) {
unsettrap(st.sig);
}
}
}
}
if exittr != 0 {
}
}
#[cfg(unix)]
#[allow(unused_variables)]
pub fn signal_suspend(sig: i32, wait_cmd: bool) -> i32 { let mut set: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe {
libc::sigemptyset(&mut set);
}
let int_state = sigtrapped.lock()
.ok()
.and_then(|g| g.get(libc::SIGINT as usize).copied())
.unwrap_or(0);
let int_trapped = (int_state & !ZSIG_IGNORED) != 0;
if !(wait_cmd || int_trapped) {
unsafe {
libc::sigaddset(&mut set, libc::SIGINT);
}
}
unsafe { libc::sigsuspend(&set) }
}
pub fn starttrapscope() { if intrap.load(Ordering::Relaxed) != 0 {
return;
}
let exit_flags = sigtrapped.lock()
.ok()
.and_then(|g| g.get(SIGEXIT as usize).copied())
.unwrap_or(0);
if exit_flags != 0 && !EXIT_TRAP_POSIX.load(Ordering::Relaxed) {
crate::ported::utils::inc_locallevel();
unsettrap(SIGEXIT); crate::ported::utils::dec_locallevel();
}
}
#[cfg(unix)]
pub fn nointr() { if is_interact() {
unsafe {
libc::signal(libc::SIGINT, libc::SIG_IGN);
}
}
}
#[cfg(unix)]
pub fn holdintr() { if is_interact() {
let mask = signal_mask(libc::SIGINT);
signal_block(&mask);
}
}
#[cfg(unix)]
pub fn noholdintr() { if is_interact() {
let mask = signal_mask(libc::SIGINT);
signal_unblock(&mask);
}
}
#[cfg(unix)]
pub fn signal_mask(sig: i32) -> libc::sigset_t {
let mut set: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe {
libc::sigemptyset(&mut set);
if sig != 0 {
libc::sigaddset(&mut set, sig);
}
}
set
}
#[cfg(unix)]
pub fn signal_setmask(set: &libc::sigset_t) -> libc::sigset_t {
let mut oset: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe {
libc::sigprocmask(libc::SIG_SETMASK, set, &mut oset);
}
oset
}
#[cfg(unix)]
pub fn wait_for_processes() -> Vec<(i32, i32)> {
let mut results = Vec::new();
loop {
let mut status: i32 = 0;
let pid = unsafe { libc::waitpid(-1, &mut status, libc::WNOHANG | libc::WUNTRACED) };
if pid <= 0 {
break;
}
results.push((pid, status));
}
results
}
#[cfg(unix)]
extern "C" fn zhandler(sig: libc::c_int) {
last_signal.store(sig, Ordering::Relaxed);
let mut newmask: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe { libc::sigfillset(&mut newmask); }
let oldmask = signal_block(&newmask);
if queueing_enabled.load(Ordering::SeqCst) != 0 {
let temp_rear = (queue_rear.load(Ordering::SeqCst) + 1) % MAX_QUEUE_SIZE;
if temp_rear != queue_front.load(Ordering::SeqCst) {
queue_rear.store(temp_rear, Ordering::SeqCst);
signal_queue[temp_rear].store(sig, Ordering::SeqCst);
if let Ok(mut g) = signal_mask_queue.lock() {
if let Some(slot) = g.get_mut(temp_rear) { *slot = oldmask; }
}
}
return;
}
let _ = signal_setmask(&oldmask);
match sig {
libc::SIGCHLD => { let _ = wait_for_processes();
}
libc::SIGPIPE => { if handletrap(libc::SIGPIPE) == 0 {
let interact =
crate::ported::zsh_h::isset(crate::ported::options::optlookup("interactive"));
if !interact {
unsafe { libc::_exit(libc::SIGPIPE); } } else {
let on_tty = unsafe { libc::isatty(0) } != 0;
if !on_tty {
crate::ported::builtin::STOPMSG .store(1, std::sync::atomic::Ordering::Relaxed);
crate::ported::builtin::zexit(
libc::SIGPIPE,
ZEXIT_SIGNAL,
); }
}
}
}
libc::SIGHUP => { if handletrap(libc::SIGHUP) == 0 {
crate::ported::builtin::STOPMSG
.store(1, std::sync::atomic::Ordering::Relaxed);
crate::ported::builtin::zexit(
libc::SIGHUP,
ZEXIT_SIGNAL,
); }
}
libc::SIGINT => { if handletrap(libc::SIGINT) == 0 {
let privileged =
crate::ported::zsh_h::isset(crate::ported::options::optlookup("privileged"));
let interactive =
crate::ported::zsh_h::isset(crate::ported::options::optlookup("interactive"));
if privileged && interactive {
crate::ported::builtin::zexit(
libc::SIGINT,
ZEXIT_SIGNAL,
);
}
let cur = crate::ported::utils::errflag
.load(std::sync::atomic::Ordering::Relaxed);
crate::ported::utils::errflag.store(
cur | ERRFLAG_INT,
std::sync::atomic::Ordering::Relaxed,
); crate::ported::builtin::LASTVAL.store(
128 + libc::SIGINT,
std::sync::atomic::Ordering::Relaxed,
); }
}
libc::SIGWINCH => { let _ = crate::ported::utils::adjustwinsize(); let _ = handletrap(libc::SIGWINCH); }
libc::SIGALRM => { if handletrap(libc::SIGALRM) == 0 {
let tmout: i64 = crate::ported::params::paramtab().read()
.ok()
.and_then(|t| {
t.get("TMOUT").and_then(|pm| {
pm.u_str.as_ref()
.and_then(|s| s.parse::<i64>().ok())
.or(Some(pm.u_val))
})
})
.unwrap_or(0); if tmout == 0 {
} else {
crate::ported::utils::errflag
.store(0, std::sync::atomic::Ordering::Relaxed);
crate::ported::utils::zwarn("timeout"); crate::ported::builtin::STOPMSG
.store(1, std::sync::atomic::Ordering::Relaxed); crate::ported::builtin::zexit(
libc::SIGALRM,
ZEXIT_SIGNAL,
); }
}
}
_ => { let _ = handletrap(sig);
}
}
}
#[cfg(unix)]
pub fn killrunjobs(from_signal: i32) {
let _ = from_signal;
}
#[cfg(unix)]
pub fn killjb(jn: i32, sig: i32) -> i32 { if jn > 0 {
unsafe { libc::killpg(jn, sig) }
} else {
-1
}
}
#[allow(non_camel_case_types)]
pub struct savetrap { pub sig: i32, pub flags: i32, pub local: i32, pub posix: i32, pub list: Option<crate::ported::zsh_h::Eprog>, }
pub static SAVETRAPS: OnceLock<Mutex<Vec<savetrap>>> = OnceLock::new();
pub static EXIT_TRAP_POSIX: AtomicBool = AtomicBool::new(false);
pub static DONTSAVETRAP: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub fn dosavetrap(sig: i32, level: i32) { let flags = sigtrapped.lock()
.ok()
.and_then(|g| g.get(sig as usize).copied())
.unwrap_or(0);
let list = siglists.lock()
.ok()
.and_then(|mut g| g.get_mut(sig as usize).and_then(|s| s.take()));
let posix = if sig == SIGEXIT {
if EXIT_TRAP_POSIX.load(Ordering::Relaxed) { 1 } else { 0 }
} else { 0 };
let st = savetrap { sig, flags, local: level, posix, list };
if let Ok(mut g) = SAVETRAPS.get_or_init(|| Mutex::new(Vec::new())).lock() {
g.insert(0, st); }
}
pub fn settrap(sig: i32, l: Option<crate::ported::zsh_h::Eprog>, flags: i32) -> i32 { if sig == -1 { return 1;
}
let jobbing = false; if jobbing && (sig == libc::SIGTTOU || sig == libc::SIGTSTP || sig == libc::SIGTTIN) {
return 1; }
queue_signals();
unsettrap(sig);
let l_is_empty = l.is_none();
if let Ok(mut g) = siglists.lock() {
if let Some(slot) = g.get_mut(sig as usize) {
*slot = l;
}
}
if (flags & ZSIG_FUNC) == 0 && l_is_empty { if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(sig as usize) { *slot = ZSIG_IGNORED; }
}
if sig != 0 && sig <= SIGCOUNT && sig != libc::SIGWINCH && sig != libc::SIGCHLD {
signal_ignore(sig); }
} else {
nsigtrapped.fetch_add(1, Ordering::Relaxed); if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(sig as usize) { *slot = ZSIG_TRAPPED; }
}
if sig != 0 && sig <= SIGCOUNT && sig != libc::SIGWINCH && sig != libc::SIGCHLD {
install_handler(sig); }
}
if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(sig as usize) { *slot |= flags; }
}
let locallevel = crate::ported::utils::locallevel() as i32;
if sig == SIGEXIT {
let posix_traps =
crate::ported::zsh_h::isset(crate::ported::options::optlookup("posixtraps")); EXIT_TRAP_POSIX.store(posix_traps, Ordering::Relaxed);
if !posix_traps {
if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(sig as usize) {
*slot |= locallevel << ZSIG_SHIFT;
}
}
}
} else if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(sig as usize) {
*slot |= locallevel << ZSIG_SHIFT;
}
}
unqueue_signals();
0 }
pub fn unsettrap(sig: i32) { let trapped = sigtrapped.lock()
.ok()
.and_then(|g| g.get(sig as usize).copied())
.unwrap_or(0);
if trapped == 0 { return; } let locallevel = crate::ported::utils::locallevel() as i32;
if DONTSAVETRAP.load(Ordering::Relaxed) == 0 && (!trapped != 0 || locallevel > (trapped >> ZSIG_SHIFT))
{
dosavetrap(sig, locallevel); }
if trapped & ZSIG_TRAPPED != 0 {
nsigtrapped.fetch_sub(1, Ordering::Relaxed); }
if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(sig as usize) { *slot = 0; } }
if let Ok(mut g) = siglists.lock() {
if let Some(slot) = g.get_mut(sig as usize) { *slot = None; }
}
if sig != 0 && sig <= SIGCOUNT && sig != libc::SIGWINCH && sig != libc::SIGCHLD {
signal_default(sig); }
}
pub fn handletrap(sig: i32) -> i32 { let idx = crate::ported::signals_h::SIGIDX(sig);
let trapped = sigtrapped.lock()
.ok()
.and_then(|g| g.get(idx as usize).copied())
.unwrap_or(0);
if trapped == 0 { return 0; }
if trap_queueing_enabled.load(Ordering::SeqCst) != 0 { let r = trap_queue_rear.load(Ordering::SeqCst);
let new_rear = (r + 1) % MAX_QUEUE_SIZE;
if new_rear != trap_queue_front.load(Ordering::SeqCst) {
trap_queue[new_rear].store(sig, Ordering::SeqCst);
trap_queue_rear.store(new_rear, Ordering::SeqCst);
}
return 1;
}
dotrap(idx);
if sig == libc::SIGALRM { }
1
}
pub fn dotrap(sig: i32) -> i32 { let trapped = sigtrapped.lock()
.ok()
.and_then(|g| g.get(sig as usize).copied())
.unwrap_or(0);
if trapped & ZSIG_IGNORED != 0 { return 0; }
if trapped & (ZSIG_TRAPPED | ZSIG_FUNC) == 0 { return 0; }
if crate::ported::utils::errflag.load(Ordering::Relaxed) != 0 { return 0; }
intrap.store(1, Ordering::SeqCst);
if sig == SIGEXIT {
in_exit_trap.store(1, Ordering::SeqCst);
}
if trapped & ZSIG_FUNC != 0 {
let signame = crate::ported::signals::getsigname(sig);
let trap_fn = format!("TRAP{}", signame);
if crate::ported::utils::getshfunc(&trap_fn).is_some() {
let args = vec![sig.to_string()];
let _ = crate::fusevm_bridge::with_executor(|exec| {
exec.dispatch_function_call(&trap_fn, &args).unwrap_or(0)
});
}
}
if sig == SIGEXIT {
in_exit_trap.store(0, Ordering::SeqCst);
}
intrap.store(0, Ordering::SeqCst);
0
}
pub fn removetrap(sig: i32) {
unsettrap(sig);
#[cfg(unix)]
unsafe {
libc::signal(sig, libc::SIG_DFL);
}
}
pub fn rtsigno(signame: i32) -> Option<i32> {
#[cfg(target_os = "linux")]
{
let sigrtmin = 34;
let sigrtmax = 64;
let sig = sigrtmin + signame;
if sig <= sigrtmax {
Some(sig)
} else {
None
}
}
#[cfg(not(target_os = "linux"))]
{
let _ = signame;
None
}
}
pub fn rtsigname(sig: i32) -> String {
#[cfg(target_os = "linux")]
{
let sigrtmin = 34;
let offset = sig - sigrtmin;
if offset == 0 {
"RTMIN".to_string()
} else if offset > 0 {
format!("RTMIN+{}", offset)
} else {
format!("SIG{}", sig)
}
}
#[cfg(not(target_os = "linux"))]
{
format!("SIG{}", sig)
}
}