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};
pub use crate::signals_h::{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 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 = crate::ported::zsh_h::isset(
crate::ported::zsh_h::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 _ = 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 shtty = crate::ported::init::SHTTY
.load(std::sync::atomic::Ordering::SeqCst);
let on_tty = shtty >= 0
&& unsafe { libc::isatty(shtty) } != 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(1); let _ = handletrap(libc::SIGWINCH); }
libc::SIGALRM => { if handletrap(libc::SIGALRM) == 0 {
let idle = crate::ported::params::ttyidlegetfn(); let tmout = crate::ported::params::getiparam("TMOUT"); if idle >= 0 && idle < tmout {
unsafe {
libc::alarm((tmout - idle) as u32); }
} else 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) { if !crate::ported::zsh_h::isset(crate::ported::zsh_h::HUP) { return;
}
let my_pid = unsafe { libc::getpid() };
let mut killed: i32 = 0;
let tab = crate::ported::jobs::JOBTAB
.get_or_init(|| std::sync::Mutex::new(Vec::new()));
let tab = tab.lock().expect("jobtab poisoned");
let thisjob = crate::ported::jobs::THISJOB
.get_or_init(|| std::sync::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 { crate::ported::utils::zwarn(&format!(
"warning: {} jobs SIGHUPed", killed)); }
}
#[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 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 = crate::ported::zsh_h::isset(crate::ported::zsh_h::MONITOR); if jobbing && (sig == libc::SIGTTOU || sig == libc::SIGTSTP || sig == libc::SIGTTIN) {
let signame = getsigname(sig);
crate::ported::utils::zerr(
&format!("can't trap SIG{} in interactive shells", signame),
);
return 1; }
queue_signals();
unsettrap(sig);
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 >= crate::ported::signals_h::VSIGCOUNT
&& sig < crate::ported::signals_h::TRAPCOUNT
{
signal_ignore(crate::ported::signals_h::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 >= crate::ported::signals_h::VSIGCOUNT
&& sig < crate::ported::signals_h::TRAPCOUNT
{
install_handler(crate::ported::signals_h::SIGNUM(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 sig == -1 { return; }
let jobbing = crate::ported::zsh_h::isset(crate::ported::zsh_h::MONITOR);
if jobbing && (sig == libc::SIGTTOU || sig == libc::SIGTSTP || sig == libc::SIGTTIN) {
return;
}
let locallevel = crate::ported::utils::locallevel() as i32;
let cond_local_or_exit = if sig == SIGEXIT {
!crate::ported::zsh_h::isset(crate::ported::zsh_h::POSIXTRAPS) } else {
crate::ported::zsh_h::isset(crate::ported::zsh_h::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 = crate::ported::zsh_h::isset(crate::ported::zsh_h::INTERACTIVE);
let forklevel: i32 =
crate::exec::FORKLEVEL.load(std::sync::atomic::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 >= crate::ported::signals_h::VSIGCOUNT
&& sig < crate::ported::signals_h::TRAPCOUNT
{
signal_default(crate::ported::signals_h::SIGNUM(sig)); }
}
}
pub use crate::ported::signals_h::{queue_signals, unqueue_signals};
pub fn removetrap(sig: i32) { unsettrap(sig);
}
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();
}
}
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 {
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 = crate::ported::signals::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::fusevm_bridge::with_executor(|exec| {
exec.dispatch_function_call(&trap_fn, &args).unwrap_or(0)
});
}
}
}
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 = crate::ported::params::getiparam("TMOUT");
if tmout > 0 {
libc::alarm(tmout as u32); }
}
}
1
}
pub fn queue_traps(wait_cmd: i32) { if !crate::ported::zsh_h::isset(crate::ported::zsh_h::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; }
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);
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 = 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.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>) { use crate::ported::builtin::{BREAKS, LASTVAL, LOCALLEVEL, LOOPS, RETFLAG, SFCONTEXT};
use crate::ported::zsh_h::{
AFTERTRAPHOOK, BEFORETRAPHOOK, EMULATE_SH, EMULATION, ERRFLAG_ERROR, ERRFLAG_INT,
SFC_SIGNAL, TRAP_STATE_FORCE_RETURN, TRAP_STATE_PRIMED, ZSIG_FUNC, ZSIG_IGNORED,
};
use crate::signals_h::{SIGDEBUG, SIGEXIT, SIGZERR};
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()
|| crate::ported::utils::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;
}
}
crate::ported::context::zcontext_save(); let saved_trap_state = crate::exec::TRAP_STATE.load(Ordering::SeqCst); let saved_trap_return = crate::exec::TRAP_RETURN.load(Ordering::SeqCst); BREAKS.store(0, Ordering::SeqCst); RETFLAG.store(0, Ordering::SeqCst); traplocallevel.store(LOCALLEVEL.load(Ordering::SeqCst), Ordering::SeqCst);
let _ = BEFORETRAPHOOK;
if (*sigtr & ZSIG_FUNC) != 0 { let osc = SFCONTEXT.load(Ordering::SeqCst); let old_incompfunc: i32 = 0;
let hn = crate::ported::jobs::gettrapnode(sig);
let mut args: Vec<String> = Vec::new(); let name = match hn {
Some(n) => crate::ported::mem::ztrdup(&n), None => { format!("TRAP{}", crate::ported::signals::getsigname(sig)) }
};
args.push(name.clone()); let num = format!("{}", sig); args.push(num);
crate::exec::TRAP_RETURN.store(-1, Ordering::SeqCst); crate::exec::TRAP_STATE.store(TRAP_STATE_PRIMED, Ordering::SeqCst); crate::ported::signals::trapisfunc.store(1, Ordering::SeqCst); isfunc = 1;
SFCONTEXT.store(SFC_SIGNAL, Ordering::SeqCst); let _ = old_incompfunc;
let fn_name = sigfn.unwrap_or("");
let _ = crate::fusevm_bridge::with_executor(|exec| {
exec.dispatch_function_call(fn_name, &args[1..]).unwrap_or(0)
});
SFCONTEXT.store(osc, Ordering::SeqCst); let _ = args; crate::ported::mem::zsfree(name); } else { crate::exec::TRAP_RETURN.store(-2, Ordering::SeqCst); crate::exec::TRAP_STATE.store(TRAP_STATE_PRIMED, Ordering::SeqCst); crate::ported::signals::trapisfunc.store(0, Ordering::SeqCst); isfunc = 0;
let _ = sigfn;
}
let _ = AFTERTRAPHOOK;
traperr = crate::ported::utils::errflag.load(Ordering::SeqCst);
new_trap_state = crate::exec::TRAP_STATE.load(Ordering::SeqCst); new_trap_return = crate::exec::TRAP_RETURN.load(Ordering::SeqCst);
crate::exec::TRAP_STATE.store(saved_trap_state, Ordering::SeqCst); crate::exec::TRAP_RETURN.store(saved_trap_return, Ordering::SeqCst); crate::ported::context::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 { crate::ported::utils::errflag.fetch_or( ERRFLAG_INT, Ordering::SeqCst,
);
} else { crate::ported::utils::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 crate::ported::r#loop::try_tryflag.load(Ordering::SeqCst) != 0 { if traperr != 0 { crate::ported::utils::errflag.fetch_or( ERRFLAG_ERROR, Ordering::SeqCst,
);
} else { crate::ported::utils::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 crate::ported::builtins::sched::zleactive.load(Ordering::SeqCst) != 0
&& crate::ported::utils::RESETNEEDED.load(Ordering::SeqCst) != 0
{
let _ = crate::ported::init::zleentry(
crate::ported::zsh_h::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<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 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 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 std::sync::atomic::AtomicBool {
static INTERACT: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
&INTERACT
}
pub fn set_interact(v: bool) {
crate::ported::options::opt_state_set("interactive", v);
}
pub fn is_interact() -> bool {
crate::ported::zsh_h::isset(crate::ported::zsh_h::INTERACTIVE)
}
#[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);
}
#[cfg(unix)]
#[test]
fn signal_mask_includes_only_requested_signal() {
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 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 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 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 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() {
use crate::ported::options::dosetopt;
use crate::ported::zsh_h::TRAPSASYNC;
let saved = crate::ported::zsh_h::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() {
use crate::ported::zsh_h::MONITOR;
use crate::ported::options::dosetopt;
let saved = crate::ported::zsh_h::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() {
use crate::ported::options::dosetopt;
use crate::ported::zsh_h::HUP;
let saved = crate::ported::zsh_h::isset(HUP);
dosetopt(HUP, 0, 0);
killrunjobs(0);
killrunjobs(1);
dosetopt(HUP, if saved { 1 } else { 0 }, 0);
}
}