pub use crate::signals_h::{signal_default, signal_ignore};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
use std::sync::{Mutex, OnceLock};
use nix::sys::signal::{
sigprocmask, SaFlags, SigAction, SigHandler, SigSet, Signal as NixSignal, SigmaskHow,
};
use nix::unistd::getpid;
use crate::DPUTS;
use crate::ported::builtin::{zexit, BREAKS, LASTVAL, LOOPS, RETFLAG, SFCONTEXT, STOPMSG};
use crate::ported::context::{zcontext_restore, zcontext_save};
use crate::ported::exec::{TRAP_RETURN, TRAP_STATE};
use crate::ported::init::zleentry;
use crate::ported::jobs::gettrapnode;
use crate::ported::mem::{zsfree, ztrdup};
use crate::ported::options::optlookup;
use crate::ported::params::{getiparam, ttyidlegetfn};
use crate::ported::signals_h::{
SIGNUM, TRAPCOUNT as TRAPCOUNT_H, VSIGCOUNT,
};
use crate::ported::utils::{
errflag, inc_locallevel, locallevel as locallevel_fn, zerr, zwarn, ERRFLAG_ERROR, RESETNEEDED,
};
use crate::ported::zsh_h::{
isset, Eprog, AFTERTRAPHOOK, BEFORETRAPHOOK, EMULATE_SH, EMULATION, ERRFLAG_INT, HUP,
INTERACTIVE, LOCALTRAPS, MONITOR, POSIXTRAPS, PRIVILEGED, SFC_SIGNAL, TRAPSASYNC,
TRAP_STATE_FORCE_RETURN, TRAP_STATE_PRIMED, ZEXIT_SIGNAL, ZLE_CMD_REFRESH, ZSIG_FUNC,
ZSIG_IGNORED, ZSIG_SHIFT, ZSIG_TRAPPED,
};
use crate::signals_h::{MAX_QUEUE_SIZE, SIGCOUNT, SIGDEBUG, SIGEXIT, SIGZERR, TRAPCOUNT};
pub use crate::ported::jobs::{getsigidx, getsigname};
pub use crate::ported::signals_h::{queue_signals, unqueue_signals};
use crate::r#loop::try_tryflag;
use crate::sched::zleactive;
use crate::utils::getshfunc;
pub static trap_queue: [AtomicI32; MAX_QUEUE_SIZE] = [ATOM_I32_ZERO; MAX_QUEUE_SIZE];
#[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);
}
}
#[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_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
}
#[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)]
#[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;
let trapsasync_set = isset(
TRAPSASYNC, );
if !(wait_cmd || trapsasync_set || int_trapped) {
unsafe {
libc::sigaddset(&mut set, libc::SIGINT);
}
}
unsafe { libc::sigsuspend(&set) }
}
#[cfg(unix)]
pub fn wait_for_processes() -> Vec<(i32, i32)> {
let mut results = Vec::new();
let waitflags = libc::WNOHANG | libc::WUNTRACED | libc::WCONTINUED; loop {
let mut status: i32 = 0;
let pid = unsafe { libc::waitpid(-1, &mut status, waitflags) };
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 reaped = wait_for_processes();
if !reaped.is_empty() {
if let Some(jt) = crate::ported::jobs::JOBTAB.get() {
if let Ok(mut guard) = jt.lock() {
for (pid, status) in reaped {
let _ = crate::ported::jobs::update_bg_job(
&mut guard, pid, status,
);
}
}
}
}
}
libc::SIGPIPE => {
if handletrap(libc::SIGPIPE) == 0 {
let interact =
isset(optlookup("interactive"));
if !interact {
unsafe {
libc::_exit(libc::SIGPIPE);
} } else {
let shtty =
crate::ported::init::SHTTY.load(Ordering::SeqCst);
let on_tty = shtty >= 0 && unsafe { libc::isatty(shtty) } != 0;
if !on_tty {
STOPMSG .store(1, Ordering::Relaxed);
zexit(libc::SIGPIPE, ZEXIT_SIGNAL);
}
}
}
}
libc::SIGHUP => {
if handletrap(libc::SIGHUP) == 0 {
STOPMSG.store(1, Ordering::Relaxed);
zexit(libc::SIGHUP, ZEXIT_SIGNAL); }
}
libc::SIGINT => {
if handletrap(libc::SIGINT) == 0 {
let privileged =
isset(optlookup("privileged"));
let interactive =
isset(optlookup("interactive"));
if privileged && interactive {
zexit(libc::SIGINT, ZEXIT_SIGNAL);
}
let cur = errflag.load(Ordering::Relaxed);
errflag
.store(cur | ERRFLAG_INT, Ordering::Relaxed); let in_list_pipe = crate::ported::exec::list_pipe
.load(Ordering::Relaxed) != 0;
let chline_nonempty = crate::ported::hist::chline
.lock()
.map(|s| !s.is_empty())
.unwrap_or(false);
let in_simple_pline = crate::ported::exec::simple_pline
.load(Ordering::Relaxed) != 0;
if in_list_pipe || chline_nonempty || in_simple_pline {
let l = crate::ported::builtin::LOOPS.load(Ordering::Relaxed);
crate::ported::builtin::BREAKS.store(l, Ordering::Relaxed);
crate::ported::input::inerrflush();
#[cfg(unix)]
if let Some(tab) = crate::ported::jobs::JOBTAB.get() {
if let Ok(jt) = tab.lock() {
crate::ported::jobs::check_cursh_sig(&jt, libc::SIGINT);
}
}
}
LASTVAL.store(128 + libc::SIGINT, Ordering::Relaxed);
}
}
libc::SIGWINCH => {
let _ = crate::ported::utils::adjustwinsize(1); let _ = handletrap(libc::SIGWINCH); }
libc::SIGALRM => {
if handletrap(libc::SIGALRM) == 0 {
let idle = ttyidlegetfn(); let tmout = getiparam("TMOUT"); if idle >= 0 && idle < tmout {
unsafe {
libc::alarm((tmout - idle) as u32); }
} else if tmout == 0 {
} else {
errflag.store(0, Ordering::Relaxed);
zwarn("timeout"); STOPMSG.store(1, Ordering::Relaxed); zexit(libc::SIGALRM, ZEXIT_SIGNAL); }
}
}
_ => {
let _ = handletrap(sig);
}
}
}
#[cfg(unix)]
pub fn killrunjobs(from_signal: i32) {
if !isset(HUP) {
return;
}
let my_pid = unsafe { libc::getpid() };
let mut killed: i32 = 0;
let tab = crate::ported::jobs::JOBTAB.get_or_init(|| Mutex::new(Vec::new()));
let tab = tab.lock().expect("jobtab poisoned");
let thisjob = crate::ported::jobs::THISJOB
.get_or_init(|| Mutex::new(-1))
.lock()
.map(|g| *g)
.unwrap_or(-1);
for (i, job) in tab.iter().enumerate().skip(1) {
if !(from_signal != 0 || i as i32 != thisjob) {
continue;
}
if (job.stat & crate::ported::jobs::stat::LOCKED) == 0 {
continue;
}
if (job.stat & crate::ported::jobs::stat::NOPRINT) != 0 {
continue;
}
if (job.stat & crate::ported::jobs::stat::STOPPED) != 0 {
continue;
}
if job.gleader != my_pid && unsafe { libc::killpg(job.gleader, libc::SIGHUP) } != -1
{
killed += 1; }
}
drop(tab);
if killed != 0 {
zwarn(&format!("warning: {} jobs SIGHUPed", killed));
}
}
#[cfg(unix)]
pub fn killjb(jn_idx: usize, sig: i32) -> i32 {
let _pn: (); let mut err: i32 = 0;
if crate::ported::zsh_h::jobbing() { let snap = {
let table = match crate::ported::jobs::JOBTAB.get() {
Some(t) => t,
None => return -1,
};
let tab = table.lock().unwrap_or_else(|e| e.into_inner());
let jn = match tab.get(jn_idx) {
Some(j) => j,
None => return -1,
};
let other_procs: Vec<libc::pid_t> = if jn.other > 0 {
tab.get(jn.other as usize)
.map(|o| o.procs.iter().map(|p| p.pid).collect())
.unwrap_or_default()
} else {
Vec::new()
};
let other_empty = jn.other > 0
&& tab
.get(jn.other as usize)
.map(|o| o.procs.is_empty())
.unwrap_or(true);
(
jn.stat,
jn.gleader,
jn.other,
jn.procs.iter().map(|p| p.pid).collect::<Vec<_>>(),
other_procs,
other_empty,
)
};
let (stat, gleader, other, procs_pids, other_procs, other_empty) = snap;
if (stat & crate::ported::zsh_h::STAT_SUPERJOB) != 0 { if sig == libc::SIGCONT { for pid in &other_procs { if unsafe { libc::killpg(*pid, sig) } == -1 { let e = std::io::Error::last_os_error().raw_os_error();
if unsafe { libc::kill(*pid, sig) } == -1
&& e != Some(libc::ESRCH)
{
err = -1; }
}
}
let n = procs_pids.len();
if n > 0 {
for pid in &procs_pids[..n - 1] { if unsafe { libc::kill(*pid, sig) } == -1
&& std::io::Error::last_os_error().raw_os_error()
!= Some(libc::ESRCH)
{
err = -1; }
}
if other_empty { let last = procs_pids[n - 1];
if unsafe { libc::kill(last, sig) } == -1
&& std::io::Error::last_os_error().raw_os_error()
!= Some(libc::ESRCH)
{
err = -1; }
}
}
if err != -1 { let table = crate::ported::jobs::JOBTAB.get().unwrap();
let mut tab = table.lock().unwrap_or_else(|e| e.into_inner());
crate::ported::jobs::makerunning(&mut tab, jn_idx); }
return err; }
let other_gleader = crate::ported::jobs::JOBTAB
.get()
.and_then(|t| t.lock().ok().and_then(|tab| tab.get(other as usize).map(|j| j.gleader)))
.unwrap_or(0);
if other_gleader > 0
&& unsafe { libc::killpg(other_gleader, sig) } == -1
&& std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH)
{
err = -1; }
if unsafe { libc::killpg(gleader, sig) } == -1 && std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH)
{
err = -1; }
return err; } else { err = unsafe { libc::killpg(gleader, sig) }; if sig == libc::SIGCONT && err != -1 { let table = crate::ported::jobs::JOBTAB.get().unwrap();
let mut tab = table.lock().unwrap_or_else(|e| e.into_inner());
crate::ported::jobs::makerunning(&mut tab, jn_idx); }
return err; }
}
let table = match crate::ported::jobs::JOBTAB.get() {
Some(t) => t,
None => return err,
};
let snap: Vec<(libc::pid_t, i32)> = {
let tab = table.lock().unwrap_or_else(|e| e.into_inner());
match tab.get(jn_idx) {
Some(j) => j.procs.iter().map(|p| (p.pid, p.status)).collect(),
None => return err,
}
};
for (pid, status) in snap { let is_running = status == crate::ported::zsh_h::SP_RUNNING;
let is_stopped = libc::WIFSTOPPED(status);
if is_running || is_stopped { let r = unsafe { libc::kill(pid, sig) };
if r == -1
&& std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH)
&& sig != 0
{
err = r;
return -1; }
err = r; }
}
err }
#[allow(non_camel_case_types)]
pub struct savetrap {
pub sig: i32, pub flags: i32, pub local: i32, pub posix: i32, pub list: Option<Eprog>, }
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<Eprog>, flags: i32) -> i32 {
if sig == -1 {
return 1;
}
let jobbing = isset(MONITOR); if jobbing && (sig == libc::SIGTTOU || sig == libc::SIGTSTP || sig == libc::SIGTTIN) {
let signame = getsigname(sig);
zerr(&format!("can't trap SIG{} in interactive shells", signame));
return 1; }
queue_signals();
unsettrap(sig);
DPUTS!(
(flags & ZSIG_FUNC) != 0 && l.is_some(), "BUG: trap function has passed eval list, too" );
let l_is_empty = match &l {
None => true,
Some(eprog) => crate::ported::parse::empty_eprog(eprog),
};
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); }
#[cfg(target_os = "linux")]
if sig >= VSIGCOUNT && sig < TRAPCOUNT_H {
signal_ignore(SIGNUM(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); }
#[cfg(target_os = "linux")]
if sig >= VSIGCOUNT && sig < TRAPCOUNT_H {
install_handler(SIGNUM(sig)); }
}
if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(sig as usize) {
*slot |= flags;
}
}
let locallevel = locallevel_fn() as i32;
if sig == SIGEXIT {
let posix_traps =
isset(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 removetrap(sig: i32) {
let trapped = sigtrapped
.lock()
.ok()
.and_then(|g| g.get(sig as usize).copied())
.unwrap_or(0);
if sig == -1 {
return;
}
let jobbing = isset(MONITOR);
if jobbing && (sig == libc::SIGTTOU || sig == libc::SIGTSTP || sig == libc::SIGTTIN) {
return;
}
let locallevel = locallevel_fn() as i32;
let cond_local_or_exit = if sig == SIGEXIT {
!isset(POSIXTRAPS) } else {
isset(LOCALTRAPS) };
if DONTSAVETRAP.load(Ordering::Relaxed) == 0 && cond_local_or_exit
&& locallevel != 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;
}
}
let interact = isset(INTERACTIVE);
let forklevel: i32 = crate::ported::exec::FORKLEVEL.load(Ordering::Relaxed); if sig == libc::SIGINT && interact {
intr();
noholdintr();
} else if sig == libc::SIGHUP {
install_handler(sig);
} else if sig == libc::SIGPIPE && interact && forklevel == 0 {
install_handler(sig);
} else if sig != 0 && sig <= SIGCOUNT && sig != libc::SIGWINCH && sig != libc::SIGCHLD {
signal_default(sig); }
#[cfg(target_os = "linux")]
{
if sig >= VSIGCOUNT && sig < TRAPCOUNT_H {
signal_default(SIGNUM(sig)); }
}
}
pub fn unsettrap(sig: i32) {
queue_signals();
removetrap(sig);
unqueue_signals();
}
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) {
inc_locallevel();
unsettrap(SIGEXIT); crate::ported::utils::dec_locallevel();
}
}
pub fn endtrapscope() {
let locallevel = locallevel_fn();
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 {
let cur_trapped = sigtrapped
.lock()
.ok()
.and_then(|g| g.get(st.sig as usize).copied())
.unwrap_or(0);
if cur_trapped != 0 {
if st.sig != SIGEXIT || !EXIT_TRAP_POSIX.load(Ordering::Relaxed) {
unsettrap(st.sig); }
}
}
}
}
if exittr != 0 && (exittr & ZSIG_FUNC) != 0 {
let signame = getsigname(SIGEXIT);
let trap_fn = format!("TRAP{}", signame);
if crate::ported::utils::getshfunc(&trap_fn).is_some() {
let args = vec![SIGEXIT.to_string()];
let _ = crate::ported::exec_hooks::dispatch_function_call(&trap_fn, &args);
}
} else if exittr != 0 {
let body = {
let signame = getsigname(SIGEXIT);
let t = crate::ported::builtin::traps_table().lock();
t.ok()
.and_then(|g| {
g.get(&signame)
.cloned()
.or_else(|| g.get("EXIT").cloned())
})
.unwrap_or_default()
};
if !body.is_empty() {
let _ = crate::ported::exec_hooks::execute_script(&body); }
}
}
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 {
#[cfg(unix)]
unsafe {
let tmout = getiparam("TMOUT");
if tmout > 0 {
libc::alarm(tmout as u32); }
}
}
1
}
pub fn queue_traps(wait_cmd: i32) {
if !isset(TRAPSASYNC) && wait_cmd == 0 {
trap_queueing_enabled.store(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);
}
}
pub fn dotrap(sig: i32) -> i32 {
let q = crate::ported::signals_h::queue_signal_level();
let trapped = sigtrapped
.lock()
.ok()
.and_then(|g| g.get(sig as usize).copied())
.unwrap_or(0);
if trapped & ZSIG_IGNORED != 0 {
return 0;
}
let signame_for_lookup = getsigname(sig);
let table_body: Option<String> = {
let aliases: &[&str] = match sig {
x if x == SIGZERR => &["ZERR", "ERR"],
x if x == SIGDEBUG => &["DEBUG"],
x if x == SIGEXIT => &["EXIT"],
_ => &[],
};
let mut found = None;
if let Ok(t) = crate::ported::builtin::traps_table().lock() {
if let Some(b) = t.get(&signame_for_lookup) {
found = Some(b.clone());
} else {
for alias in aliases {
if let Some(b) = t.get(*alias) {
found = Some(b.clone());
break;
}
}
}
}
found
};
if trapped & (ZSIG_TRAPPED | ZSIG_FUNC) == 0 && table_body.is_none() {
return 0;
}
if errflag.load(Ordering::Relaxed) != 0 {
return 0;
}
intrap.store(1, Ordering::SeqCst);
crate::ported::signals_h::dont_queue_signals();
if sig == SIGEXIT {
in_exit_trap.fetch_add(1, Ordering::SeqCst); }
if trapped & ZSIG_FUNC != 0 {
let signame = getsigname(sig);
let trap_fn = format!("TRAP{}", signame);
if getshfunc(&trap_fn).is_some() {
let args = vec![sig.to_string()];
let _ = crate::ported::exec_hooks::dispatch_function_call(&trap_fn, &args);
}
}
if let Some(body) = table_body {
let _ = crate::ported::exec_hooks::execute_script(&body);
}
if sig == SIGEXIT {
in_exit_trap.fetch_sub(1, Ordering::SeqCst); }
crate::ported::signals_h::restore_queue_signals(q); intrap.store(0, Ordering::SeqCst);
0
}
#[allow(clippy::too_many_arguments)]
pub fn dotrapargs(sig: i32, sigtr: &mut i32, sigfn: Option<&str>) {
let obreaks: i32 = BREAKS.load(Ordering::SeqCst); let oretflag: i32 = RETFLAG.load(Ordering::SeqCst); let olastval: i32 = LASTVAL.load(Ordering::SeqCst); let isfunc: i32; let traperr: i32; let new_trap_state: i32; let new_trap_return: i32;
if (*sigtr & ZSIG_IGNORED) != 0 || sigfn.is_none()
|| errflag.load(Ordering::SeqCst) != 0
{
return; }
if intrap.load(Ordering::SeqCst) != 0 {
if sig == SIGEXIT || sig == SIGDEBUG || sig == SIGZERR {
return; }
}
queue_signals();
intrap.fetch_add(1, Ordering::SeqCst); *sigtr |= ZSIG_IGNORED; if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(sig as usize) {
*slot |= ZSIG_IGNORED;
}
}
zcontext_save(); let saved_trap_state = TRAP_STATE.load(Ordering::SeqCst); let saved_trap_return = TRAP_RETURN.load(Ordering::SeqCst); BREAKS.store(0, Ordering::SeqCst); RETFLAG.store(0, Ordering::SeqCst); traplocallevel.store(crate::ported::params::locallevel.load(Ordering::SeqCst), Ordering::SeqCst);
let hd = crate::ported::module::gethookdef("BEFORETRAPHOOK");
if !hd.is_null() {
let _ = crate::ported::module::runhookdef(hd, std::ptr::null_mut());
}
let _ = BEFORETRAPHOOK;
if (*sigtr & ZSIG_FUNC) != 0 {
let osc = SFCONTEXT.load(Ordering::SeqCst); let old_incompfunc: i32 =
crate::ported::zle::complete::INCOMPFUNC.load(Ordering::Relaxed);
let hn = gettrapnode(sig, false);
let mut args: Vec<String> = Vec::new(); let name = match hn {
Some(n) => ztrdup(&n), None => {
format!("TRAP{}", getsigname(sig)) }
};
args.push(name.clone()); let num = format!("{}", sig); args.push(num);
TRAP_RETURN.store(-1, Ordering::SeqCst); TRAP_STATE.store(TRAP_STATE_PRIMED, Ordering::SeqCst); trapisfunc.store(1, Ordering::SeqCst); isfunc = 1;
SFCONTEXT.store(SFC_SIGNAL, Ordering::SeqCst); crate::ported::zle::complete::INCOMPFUNC.store(0, Ordering::Relaxed);
let fn_name = sigfn.unwrap_or("");
let shf_clone: Option<crate::ported::zsh_h::shfunc> = {
let tab = crate::ported::hashtable::shfunctab_lock().read();
tab.ok().and_then(|t| t.get(fn_name).cloned())
};
if let Some(mut shf) = shf_clone {
crate::ported::exec::execshfunc(&mut shf, &mut args);
}
SFCONTEXT.store(osc, Ordering::SeqCst); crate::ported::zle::complete::INCOMPFUNC.store(old_incompfunc, Ordering::Relaxed);
let _ = args; zsfree(name); } else {
TRAP_RETURN.store(-2, Ordering::SeqCst); TRAP_STATE.store(TRAP_STATE_PRIMED, Ordering::SeqCst); trapisfunc.store(0, Ordering::SeqCst); isfunc = 0;
if let Some(src) = sigfn {
let _ = crate::ported::exec_hooks::execute_script_zsh_pipeline(src);
}
}
let hd = crate::ported::module::gethookdef("AFTERTRAPHOOK");
if !hd.is_null() {
let _ = crate::ported::module::runhookdef(hd, std::ptr::null_mut());
}
let _ = AFTERTRAPHOOK;
traperr = errflag.load(Ordering::SeqCst);
new_trap_state = TRAP_STATE.load(Ordering::SeqCst); new_trap_return = TRAP_RETURN.load(Ordering::SeqCst);
TRAP_STATE.store(saved_trap_state, Ordering::SeqCst); TRAP_RETURN.store(saved_trap_return, Ordering::SeqCst); zcontext_restore();
if new_trap_state == TRAP_STATE_FORCE_RETURN && !(isfunc != 0 && new_trap_return == 0)
{
if isfunc != 0 {
BREAKS.store(LOOPS.load(Ordering::SeqCst), Ordering::SeqCst); if sig == libc::SIGINT || sig == libc::SIGQUIT {
errflag.fetch_or(
ERRFLAG_INT,
Ordering::SeqCst,
);
} else {
errflag.fetch_or(
ERRFLAG_ERROR,
Ordering::SeqCst,
);
}
}
LASTVAL.store(new_trap_return, Ordering::SeqCst); RETFLAG.store(1, Ordering::SeqCst); } else {
if traperr != 0 && !EMULATION(EMULATE_SH) {
LASTVAL.store(1, Ordering::SeqCst); } else {
LASTVAL.store(olastval, Ordering::SeqCst); }
if try_tryflag.load(Ordering::SeqCst) != 0 {
if traperr != 0 {
errflag.fetch_or(
ERRFLAG_ERROR,
Ordering::SeqCst,
);
} else {
errflag.fetch_and(
!ERRFLAG_ERROR,
Ordering::SeqCst,
);
}
}
BREAKS.fetch_add(obreaks, Ordering::SeqCst); RETFLAG.store(oretflag, Ordering::SeqCst); let cur_breaks = BREAKS.load(Ordering::SeqCst);
let cur_loops = LOOPS.load(Ordering::SeqCst);
if cur_breaks > cur_loops {
BREAKS.store(cur_loops, Ordering::SeqCst); }
}
if zleactive.load(Ordering::SeqCst) != 0
&& RESETNEEDED.load(Ordering::SeqCst) != 0
{
let _ = zleentry(ZLE_CMD_REFRESH);
}
if *sigtr != ZSIG_IGNORED {
*sigtr &= !ZSIG_IGNORED; if let Ok(mut g) = sigtrapped.lock() {
if let Some(slot) = g.get_mut(sig as usize) {
*slot &= !ZSIG_IGNORED;
}
}
}
intrap.fetch_sub(1, Ordering::SeqCst);
unqueue_signals(); }
pub fn rtsigno(signame: &str) -> Option<i32> {
#[cfg(target_os = "linux")]
{
let sigrtmin = libc::SIGRTMIN();
let sigrtmax = libc::SIGRTMAX();
let maxofs = sigrtmax - sigrtmin;
let (sig, dir, op): (i32, i32, char) = if let Some(rest) = signame.strip_prefix("RTMIN") {
(sigrtmin, 1, '+') } else if let Some(rest) = signame.strip_prefix("RTMAX") {
(sigrtmax, -1, '-') } else {
return None; };
let rest = if signame.starts_with("RTMIN") {
&signame[5..]
} else {
&signame[5..]
};
let mut final_sig = sig;
if !rest.is_empty() {
if rest.starts_with(op) {
let num_str = &rest[1..];
let offset: i32 = match num_str.parse() {
Ok(n) => n,
Err(_) => return None, };
if offset > maxofs {
return None; }
final_sig += offset * dir;
} else {
return None;
}
}
Some(final_sig)
}
#[cfg(not(target_os = "linux"))]
{
let _ = signame;
None
}
}
pub fn rtsigname(sig: i32) -> String {
#[cfg(target_os = "linux")]
{
let sigrtmin = libc::SIGRTMIN();
let sigrtmax = libc::SIGRTMAX();
if sig < sigrtmin || sig > sigrtmax {
return String::new();
}
let minofs = sig - sigrtmin;
let maxofs = sigrtmax - sig;
let form = maxofs < minofs;
let prefix = if form { "RTMAX-" } else { "RTMIN+" };
let offset = if form { maxofs } else { minofs };
if offset == 0 {
prefix[..5].to_string()
} else {
format!("{}{}", prefix, offset)
}
}
#[cfg(not(target_os = "linux"))]
{
let _ = sig;
String::new()
}
}
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 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<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 static SAVETRAPS: OnceLock<Mutex<Vec<savetrap>>> = OnceLock::new();
pub static EXIT_TRAP_POSIX: AtomicBool = AtomicBool::new(false);
pub static DONTSAVETRAP: AtomicI32 = AtomicI32::new(0);
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) }
}
fn interact_lock() -> &'static AtomicBool {
static INTERACT: AtomicBool = AtomicBool::new(false);
&INTERACT
}
pub fn set_interact(v: bool) {
crate::ported::options::opt_state_set("interactive", v);
}
pub fn is_interact() -> bool {
isset(INTERACTIVE)
}
#[cfg(test)]
mod tests {
use crate::options::dosetopt;
use crate::zsh_h::{HUP, MONITOR, TRAPSASYNC};
use super::*;
#[test]
fn test_sig_by_name() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
assert_eq!(getsigname(libc::SIGINT), "INT");
assert_eq!(getsigname(libc::SIGHUP), "HUP");
assert_eq!(getsigname(SIGEXIT), "EXIT");
}
#[test]
fn test_signal_queue() {
let _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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 _g = crate::test_util::global_state_lock();
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);
}
#[cfg(unix)]
#[test]
fn signal_mask_includes_only_requested_signal() {
let _g = crate::test_util::global_state_lock();
let m = signal_mask(libc::SIGUSR1);
assert_eq!(
unsafe { libc::sigismember(&m, libc::SIGUSR1) },
1,
"c:166 — requested signal must be set"
);
assert_eq!(unsafe { libc::sigismember(&m, libc::SIGUSR2) }, 0);
assert_eq!(unsafe { libc::sigismember(&m, libc::SIGTERM) }, 0);
}
#[cfg(unix)]
#[test]
fn signal_mask_with_zero_returns_empty_set() {
let _g = crate::test_util::global_state_lock();
let m = signal_mask(0);
for sig in [libc::SIGINT, libc::SIGTERM, libc::SIGUSR1, libc::SIGUSR2] {
assert_eq!(
unsafe { libc::sigismember(&m, sig) },
0,
"c:163 — sig=0 produces empty set, but {} found",
sig
);
}
}
#[cfg(target_os = "linux")]
#[test]
fn rtsigno_parses_rt_signal_names() {
let _g = crate::test_util::global_state_lock();
let sigrtmin = libc::SIGRTMIN();
let sigrtmax = libc::SIGRTMAX();
assert_eq!(rtsigno("RTMIN"), Some(sigrtmin), "c:1300 — bare RTMIN");
assert_eq!(rtsigno("RTMAX"), Some(sigrtmax), "c:1302 — bare RTMAX");
assert_eq!(
rtsigno("RTMIN+1"),
Some(sigrtmin + 1),
"c:1307-1311 — RTMIN+N"
);
assert_eq!(
rtsigno("RTMAX-1"),
Some(sigrtmax - 1),
"c:1307-1311 — RTMAX-N"
);
assert_eq!(rtsigno("SIGINT"), None, "c:1304 — non-RT name returns None");
assert_eq!(rtsigno(""), None, "empty string returns None");
let maxofs = sigrtmax - sigrtmin;
assert_eq!(
rtsigno(&format!("RTMIN+{}", maxofs + 1)),
None,
"c:1310 — offset > maxofs returns None"
);
assert_eq!(
rtsigno("RTMINx"),
None,
"c:1313 — trailing non-op char returns None"
);
}
#[cfg(target_os = "linux")]
#[test]
fn rtsigname_picks_shorter_form_between_rtmin_rtmax() {
let _g = crate::test_util::global_state_lock();
let sigrtmin = libc::SIGRTMIN();
let sigrtmax = libc::SIGRTMAX();
assert_eq!(
rtsigname(sigrtmin),
"RTMIN",
"c:1334 — offset 0 → bare 'RTMIN' (no '+0')"
);
assert_eq!(
rtsigname(sigrtmax),
"RTMAX",
"c:1334 — offset 0 → bare 'RTMAX' (no '-0')"
);
assert_eq!(
rtsigname(sigrtmin + 1),
"RTMIN+1",
"c:1322 — minofs < maxofs → form=0 → RTMIN+1"
);
assert_eq!(
rtsigname(sigrtmax - 1),
"RTMAX-1",
"c:1322 — maxofs < minofs → form=1 → RTMAX-1"
);
assert_eq!(
rtsigname(sigrtmin - 1),
"",
"c:1326 — signo < SIGRTMIN → NULL (empty)"
);
assert_eq!(
rtsigname(sigrtmax + 1),
"",
"c:1326 — signo > SIGRTMAX → NULL (empty)"
);
}
#[cfg(unix)]
#[test]
fn wait_for_processes_uses_canonical_waitpid_flags() {
let _g = crate::test_util::global_state_lock();
let canonical = libc::WNOHANG | libc::WUNTRACED | libc::WCONTINUED;
assert_ne!(libc::WNOHANG, 0);
assert_ne!(libc::WUNTRACED, 0);
assert_ne!(libc::WCONTINUED, 0);
assert_eq!(
libc::WNOHANG & libc::WUNTRACED,
0,
"WNOHANG and WUNTRACED must be disjoint bits"
);
assert_eq!(
libc::WNOHANG & libc::WCONTINUED,
0,
"WNOHANG and WCONTINUED must be disjoint bits"
);
assert_eq!(
libc::WUNTRACED & libc::WCONTINUED,
0,
"WUNTRACED and WCONTINUED must be disjoint bits"
);
assert!(
canonical >= libc::WNOHANG + libc::WUNTRACED + libc::WCONTINUED,
"canonical mask must include all three bits"
);
let result = wait_for_processes();
assert!(
result.is_empty(),
"no child process to reap in test — must return empty"
);
}
#[cfg(unix)]
#[test]
fn queue_traps_respects_trapsasync_and_wait_cmd() {
let _g = crate::test_util::global_state_lock();
let saved = isset(TRAPSASYNC);
dosetopt(TRAPSASYNC, 0, 0);
trap_queueing_enabled.store(0, Ordering::SeqCst);
queue_traps(1);
assert_eq!(
trap_queueing_enabled.load(Ordering::SeqCst),
0,
"c:1026 — wait_cmd=1 gate must block queueing"
);
queue_traps(0);
assert_eq!(
trap_queueing_enabled.load(Ordering::SeqCst),
1,
"c:1031 — both gates off → queueing enabled = 1"
);
trap_queueing_enabled.store(0, Ordering::SeqCst);
dosetopt(TRAPSASYNC, 1, 0);
queue_traps(0);
assert_eq!(
trap_queueing_enabled.load(Ordering::SeqCst),
0,
"c:1026 — TRAPSASYNC=on must block queueing even with wait_cmd=0"
);
dosetopt(TRAPSASYNC, if saved { 1 } else { 0 }, 0);
trap_queueing_enabled.store(0, Ordering::SeqCst);
}
#[cfg(unix)]
#[test]
fn settrap_rejects_job_control_signals_when_monitor_set() {
let _g = crate::test_util::global_state_lock();
let saved = isset(MONITOR);
dosetopt(MONITOR, 0, 0);
assert_eq!(
settrap(libc::SIGTSTP, None, 0),
0,
"c:696 — MONITOR off → settrap on SIGTSTP succeeds"
);
unsettrap(libc::SIGTSTP);
dosetopt(MONITOR, 1, 1);
assert_eq!(
settrap(libc::SIGTSTP, None, 0),
1,
"c:696-699 — MONITOR on → settrap on SIGTSTP rejected"
);
assert_eq!(
settrap(libc::SIGTTOU, None, 0),
1,
"c:696-699 — SIGTTOU also rejected under MONITOR"
);
assert_eq!(
settrap(libc::SIGTTIN, None, 0),
1,
"c:696-699 — SIGTTIN also rejected under MONITOR"
);
dosetopt(MONITOR, if saved { 1 } else { 0 }, 1);
}
#[cfg(unix)]
#[test]
fn killrunjobs_short_circuits_when_hup_unset() {
let _g = crate::test_util::global_state_lock();
let saved = isset(HUP);
dosetopt(HUP, 0, 0);
killrunjobs(0);
killrunjobs(1);
dosetopt(HUP, if saved { 1 } else { 0 }, 0);
}
}