Skip to main content

zsh/
signals.rs

1//! Signal handling for zshrs
2//!
3//! Direct port from zsh/Src/signals.c
4//!
5//! Manages signal handling including:
6//! - Signal handlers for SIGINT, SIGCHLD, SIGHUP, etc.
7//! - Signal queueing during critical sections
8//! - Trap management (trap builtin)
9//! - Job control signals
10
11use nix::sys::signal::{sigprocmask, SigmaskHow};
12use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal as NixSignal};
13use nix::unistd::getpid;
14use std::collections::HashMap;
15use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
16use std::sync::{Mutex, OnceLock};
17
18/// Maximum size of signal queue
19const MAX_QUEUE_SIZE: usize = 128;
20
21/// Signal trap flags
22pub mod trap_flags {
23    pub const ZSIG_TRAPPED: u32 = 1; // Signal is trapped
24    pub const ZSIG_IGNORED: u32 = 2; // Signal is being ignored
25    pub const ZSIG_FUNC: u32 = 4; // Trap is a function (TRAPXXX)
26    pub const ZSIG_SHIFT: u32 = 3; // Bits to shift for local level
27}
28
29/// Well-known signal numbers (matching libc on most Unix systems)
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31#[repr(i32)]
32pub enum Signal {
33    SIGHUP = libc::SIGHUP,
34    SIGINT = libc::SIGINT,
35    SIGQUIT = libc::SIGQUIT,
36    SIGILL = libc::SIGILL,
37    SIGTRAP = libc::SIGTRAP,
38    SIGABRT = libc::SIGABRT,
39    SIGBUS = libc::SIGBUS,
40    SIGFPE = libc::SIGFPE,
41    SIGKILL = libc::SIGKILL,
42    SIGUSR1 = libc::SIGUSR1,
43    SIGSEGV = libc::SIGSEGV,
44    SIGUSR2 = libc::SIGUSR2,
45    SIGPIPE = libc::SIGPIPE,
46    SIGALRM = libc::SIGALRM,
47    SIGTERM = libc::SIGTERM,
48    SIGCHLD = libc::SIGCHLD,
49    SIGCONT = libc::SIGCONT,
50    SIGSTOP = libc::SIGSTOP,
51    SIGTSTP = libc::SIGTSTP,
52    SIGTTIN = libc::SIGTTIN,
53    SIGTTOU = libc::SIGTTOU,
54    SIGURG = libc::SIGURG,
55    SIGXCPU = libc::SIGXCPU,
56    SIGXFSZ = libc::SIGXFSZ,
57    SIGVTALRM = libc::SIGVTALRM,
58    SIGPROF = libc::SIGPROF,
59    SIGWINCH = libc::SIGWINCH,
60    SIGIO = libc::SIGIO,
61    SIGSYS = libc::SIGSYS,
62}
63
64/// Pseudo-signals for shell traps
65pub const SIGEXIT: i32 = 0;
66pub const SIGDEBUG: i32 = -1;
67pub const SIGZERR: i32 = -2;
68
69/// Signal names array
70pub static SIGNAL_NAMES: &[(&str, i32)] = &[
71    ("EXIT", SIGEXIT),
72    ("HUP", libc::SIGHUP),
73    ("INT", libc::SIGINT),
74    ("QUIT", libc::SIGQUIT),
75    ("ILL", libc::SIGILL),
76    ("TRAP", libc::SIGTRAP),
77    ("ABRT", libc::SIGABRT),
78    ("BUS", libc::SIGBUS),
79    ("FPE", libc::SIGFPE),
80    ("KILL", libc::SIGKILL),
81    ("USR1", libc::SIGUSR1),
82    ("SEGV", libc::SIGSEGV),
83    ("USR2", libc::SIGUSR2),
84    ("PIPE", libc::SIGPIPE),
85    ("ALRM", libc::SIGALRM),
86    ("TERM", libc::SIGTERM),
87    ("CHLD", libc::SIGCHLD),
88    ("CONT", libc::SIGCONT),
89    ("STOP", libc::SIGSTOP),
90    ("TSTP", libc::SIGTSTP),
91    ("TTIN", libc::SIGTTIN),
92    ("TTOU", libc::SIGTTOU),
93    ("URG", libc::SIGURG),
94    ("XCPU", libc::SIGXCPU),
95    ("XFSZ", libc::SIGXFSZ),
96    ("VTALRM", libc::SIGVTALRM),
97    ("PROF", libc::SIGPROF),
98    ("WINCH", libc::SIGWINCH),
99    ("IO", libc::SIGIO),
100    ("SYS", libc::SIGSYS),
101    ("DEBUG", SIGDEBUG),
102    ("ZERR", SIGZERR),
103    ("ERR", SIGZERR), // Alias
104];
105
106/// Get signal number from name
107pub fn sig_by_name(name: &str) -> Option<i32> {
108    let name_upper = name.to_uppercase();
109    let lookup = if name_upper.starts_with("SIG") {
110        &name_upper[3..]
111    } else {
112        &name_upper
113    };
114
115    for (sig_name, sig_num) in SIGNAL_NAMES {
116        if *sig_name == lookup {
117            return Some(*sig_num);
118        }
119    }
120
121    // Try parsing as number
122    lookup.parse().ok()
123}
124
125/// Get signal name from number
126pub fn sig_name(sig: i32) -> Option<&'static str> {
127    for (name, num) in SIGNAL_NAMES {
128        if *num == sig {
129            return Some(name);
130        }
131    }
132    None
133}
134
135/// Signal state for queueing
136struct SignalQueue {
137    enabled: AtomicBool,
138    front: AtomicUsize,
139    rear: AtomicUsize,
140    signals: [AtomicI32; MAX_QUEUE_SIZE],
141}
142
143impl SignalQueue {
144    const fn new() -> Self {
145        const INIT: AtomicI32 = AtomicI32::new(0);
146        SignalQueue {
147            enabled: AtomicBool::new(false),
148            front: AtomicUsize::new(0),
149            rear: AtomicUsize::new(0),
150            signals: [INIT; MAX_QUEUE_SIZE],
151        }
152    }
153
154    fn is_enabled(&self) -> bool {
155        self.enabled.load(Ordering::SeqCst)
156    }
157
158    fn enable(&self) {
159        self.enabled.store(true, Ordering::SeqCst);
160    }
161
162    fn disable(&self) {
163        self.enabled.store(false, Ordering::SeqCst);
164    }
165
166    fn push(&self, sig: i32) -> bool {
167        let rear = self.rear.load(Ordering::SeqCst);
168        let new_rear = (rear + 1) % MAX_QUEUE_SIZE;
169        let front = self.front.load(Ordering::SeqCst);
170
171        if new_rear == front {
172            return false; // Queue full
173        }
174
175        self.signals[new_rear].store(sig, Ordering::SeqCst);
176        self.rear.store(new_rear, Ordering::SeqCst);
177        true
178    }
179
180    fn pop(&self) -> Option<i32> {
181        let front = self.front.load(Ordering::SeqCst);
182        let rear = self.rear.load(Ordering::SeqCst);
183
184        if front == rear {
185            return None; // Queue empty
186        }
187
188        let new_front = (front + 1) % MAX_QUEUE_SIZE;
189        let sig = self.signals[new_front].load(Ordering::SeqCst);
190        self.front.store(new_front, Ordering::SeqCst);
191        Some(sig)
192    }
193}
194
195static SIGNAL_QUEUE: SignalQueue = SignalQueue::new();
196static TRAP_QUEUE: SignalQueue = SignalQueue::new();
197
198/// Last signal received
199static LAST_SIGNAL: AtomicI32 = AtomicI32::new(0);
200
201/// Trap handler storage
202pub struct TrapHandler {
203    /// Trap code/function for each signal
204    traps: Mutex<HashMap<i32, TrapAction>>,
205    /// Flags for each trapped signal
206    flags: Mutex<HashMap<i32, u32>>,
207    /// Number of trapped signals
208    pub num_trapped: AtomicUsize,
209    /// Currently in a trap?
210    pub in_trap: AtomicBool,
211    /// Running exit trap?
212    pub in_exit_trap: AtomicBool,
213}
214
215/// What action to take for a trap
216#[derive(Debug, Clone)]
217pub enum TrapAction {
218    /// Ignore the signal
219    Ignore,
220    /// Execute this code string
221    Code(String),
222    /// Call function TRAPXXX
223    Function(String),
224    /// Default action
225    Default,
226}
227
228impl Default for TrapHandler {
229    fn default() -> Self {
230        Self::new()
231    }
232}
233
234impl TrapHandler {
235    pub fn new() -> Self {
236        TrapHandler {
237            traps: Mutex::new(HashMap::new()),
238            flags: Mutex::new(HashMap::new()),
239            num_trapped: AtomicUsize::new(0),
240            in_trap: AtomicBool::new(false),
241            in_exit_trap: AtomicBool::new(false),
242        }
243    }
244
245    /// Set a trap for a signal
246    pub fn set_trap(&self, sig: i32, action: TrapAction) -> Result<(), String> {
247        // Can't trap SIGKILL or SIGSTOP
248        if sig == libc::SIGKILL || sig == libc::SIGSTOP {
249            return Err(format!("can't trap SIG{}", sig_name(sig).unwrap_or("?")));
250        }
251
252        let mut traps = self.traps.lock().unwrap();
253        let mut flags = self.flags.lock().unwrap();
254
255        let was_trapped = flags
256            .get(&sig)
257            .map(|f| f & trap_flags::ZSIG_TRAPPED != 0)
258            .unwrap_or(false);
259
260        match &action {
261            TrapAction::Ignore => {
262                traps.insert(sig, action);
263                flags.insert(sig, trap_flags::ZSIG_IGNORED);
264                if sig > 0 {
265                    self.ignore_signal(sig);
266                }
267            }
268            TrapAction::Code(code) if code.is_empty() => {
269                traps.insert(sig, TrapAction::Ignore);
270                flags.insert(sig, trap_flags::ZSIG_IGNORED);
271                if sig > 0 {
272                    self.ignore_signal(sig);
273                }
274            }
275            TrapAction::Code(_) => {
276                if !was_trapped {
277                    self.num_trapped.fetch_add(1, Ordering::SeqCst);
278                }
279                traps.insert(sig, action);
280                flags.insert(sig, trap_flags::ZSIG_TRAPPED);
281                if sig > 0 {
282                    self.install_handler(sig);
283                }
284            }
285            TrapAction::Function(name) => {
286                if !was_trapped {
287                    self.num_trapped.fetch_add(1, Ordering::SeqCst);
288                }
289                traps.insert(sig, TrapAction::Function(name.clone()));
290                flags.insert(sig, trap_flags::ZSIG_TRAPPED | trap_flags::ZSIG_FUNC);
291                if sig > 0 {
292                    self.install_handler(sig);
293                }
294            }
295            TrapAction::Default => {
296                if was_trapped {
297                    self.num_trapped.fetch_sub(1, Ordering::SeqCst);
298                }
299                traps.remove(&sig);
300                flags.remove(&sig);
301                if sig > 0 {
302                    self.default_signal(sig);
303                }
304            }
305        }
306
307        Ok(())
308    }
309
310    /// Remove a trap
311    pub fn unset_trap(&self, sig: i32) {
312        let _ = self.set_trap(sig, TrapAction::Default);
313    }
314
315    /// Get the trap action for a signal
316    pub fn get_trap(&self, sig: i32) -> Option<TrapAction> {
317        self.traps.lock().unwrap().get(&sig).cloned()
318    }
319
320    /// Check if a signal is trapped
321    pub fn is_trapped(&self, sig: i32) -> bool {
322        self.flags
323            .lock()
324            .unwrap()
325            .get(&sig)
326            .map(|f| f & trap_flags::ZSIG_TRAPPED != 0)
327            .unwrap_or(false)
328    }
329
330    /// Check if a signal is ignored
331    pub fn is_ignored(&self, sig: i32) -> bool {
332        self.flags
333            .lock()
334            .unwrap()
335            .get(&sig)
336            .map(|f| f & trap_flags::ZSIG_IGNORED != 0)
337            .unwrap_or(false)
338    }
339
340    /// Install signal handler
341    fn install_handler(&self, sig: i32) {
342        unsafe {
343            libc::signal(sig, handler as *const () as usize);
344        }
345    }
346
347    /// Ignore a signal
348    fn ignore_signal(&self, sig: i32) {
349        unsafe {
350            libc::signal(sig, libc::SIG_IGN);
351        }
352    }
353
354    /// Reset to default handler
355    fn default_signal(&self, sig: i32) {
356        unsafe {
357            libc::signal(sig, libc::SIG_DFL);
358        }
359    }
360
361    /// List all traps
362    pub fn list_traps(&self) -> Vec<(i32, TrapAction)> {
363        self.traps
364            .lock()
365            .unwrap()
366            .iter()
367            .map(|(k, v)| (*k, v.clone()))
368            .collect()
369    }
370}
371
372/// Global trap handler
373static TRAPS: OnceLock<TrapHandler> = OnceLock::new();
374
375/// Get the global trap handler
376pub fn traps() -> &'static TrapHandler {
377    TRAPS.get_or_init(TrapHandler::new)
378}
379
380/// Store the main shell PID to detect forked children.
381static MAIN_PID: AtomicI32 = AtomicI32::new(0);
382
383/// Whether we received SIGCHLD.
384static SIGCHLD_RECEIVED: AtomicBool = AtomicBool::new(false);
385
386/// Whether we received SIGWINCH.
387static SIGWINCH_RECEIVED: AtomicBool = AtomicBool::new(false);
388
389/// True iff the current pid differs from the main shell pid — i.e.
390/// we're inside a forked child (pipeline stage, async, etc.).
391/// Worker pool threads, signal handlers, and resources tied to
392/// the original process should not be used here.
393pub fn is_forked_child() -> bool {
394    // Lazy-init MAIN_PID to current pid the first time this is called.
395    // signal_set_handlers also sets it, but -c mode doesn't always
396    // call that path. The first caller wins; subsequent forks see a
397    // different pid and return true.
398    let mut main = MAIN_PID.load(Ordering::Relaxed);
399    if main == 0 {
400        let cur = getpid().as_raw();
401        match MAIN_PID.compare_exchange(0, cur, Ordering::Relaxed, Ordering::Relaxed) {
402            Ok(_) => main = cur,
403            Err(prev) => main = prev,
404        }
405    }
406    getpid().as_raw() != main
407}
408
409/// Check if we're in a forked child and re-raise signal if so.
410fn reraise_if_forked_child(sig: i32) -> bool {
411    if getpid().as_raw() == MAIN_PID.load(Ordering::Relaxed) {
412        return false;
413    }
414    unsafe {
415        libc::signal(sig, libc::SIG_DFL);
416        libc::raise(sig);
417    }
418    true
419}
420
421/// Signal handler function
422extern "C" fn handler(sig: i32) {
423    // Preserve errno
424    #[cfg(target_os = "macos")]
425    let saved_errno = unsafe { *libc::__error() };
426    #[cfg(not(target_os = "macos"))]
427    let saved_errno = unsafe { *libc::__errno_location() };
428
429    // Check if we're a forked child
430    if reraise_if_forked_child(sig) {
431        #[cfg(target_os = "macos")]
432        unsafe {
433            *libc::__error() = saved_errno
434        };
435        #[cfg(not(target_os = "macos"))]
436        unsafe {
437            *libc::__errno_location() = saved_errno
438        };
439        return;
440    }
441
442    LAST_SIGNAL.store(sig, Ordering::SeqCst);
443
444    // Track specific signals
445    if sig == libc::SIGCHLD {
446        SIGCHLD_RECEIVED.store(true, Ordering::SeqCst);
447    } else if sig == libc::SIGWINCH {
448        SIGWINCH_RECEIVED.store(true, Ordering::SeqCst);
449    }
450
451    // If queueing is enabled, queue the signal
452    if SIGNAL_QUEUE.is_enabled() {
453        SIGNAL_QUEUE.push(sig);
454        #[cfg(target_os = "macos")]
455        unsafe {
456            *libc::__error() = saved_errno
457        };
458        #[cfg(not(target_os = "macos"))]
459        unsafe {
460            *libc::__errno_location() = saved_errno
461        };
462        return;
463    }
464
465    // Handle the signal directly
466    handle_signal(sig);
467
468    #[cfg(target_os = "macos")]
469    unsafe {
470        *libc::__error() = saved_errno
471    };
472    #[cfg(not(target_os = "macos"))]
473    unsafe {
474        *libc::__errno_location() = saved_errno
475    };
476}
477
478/// Handle a signal
479fn handle_signal(sig: i32) {
480    match sig {
481        s if s == libc::SIGCHLD => {
482            // Child process status change - handled by job control
483        }
484        s if s == libc::SIGINT => {
485            // Interrupt - set error flag
486            if let Some(action) = traps().get_trap(s) {
487                run_trap(s, &action);
488            }
489        }
490        s if s == libc::SIGHUP => {
491            // Hangup
492            if let Some(action) = traps().get_trap(s) {
493                run_trap(s, &action);
494            }
495        }
496        s if s == libc::SIGWINCH => {
497            // Window size change
498            if let Some(action) = traps().get_trap(s) {
499                run_trap(s, &action);
500            }
501        }
502        s if s == libc::SIGALRM => {
503            // Alarm
504            if let Some(action) = traps().get_trap(s) {
505                run_trap(s, &action);
506            }
507        }
508        s if s == libc::SIGPIPE => {
509            // Broken pipe
510            if let Some(action) = traps().get_trap(s) {
511                run_trap(s, &action);
512            }
513        }
514        _ => {
515            // Other signals
516            if let Some(action) = traps().get_trap(sig) {
517                run_trap(sig, &action);
518            }
519        }
520    }
521}
522
523/// Run a trap action
524fn run_trap(sig: i32, action: &TrapAction) {
525    match action {
526        TrapAction::Ignore => {}
527        TrapAction::Code(_code) => {
528            // Would execute the code - needs executor integration
529            traps().in_trap.store(true, Ordering::SeqCst);
530            if sig == SIGEXIT {
531                traps().in_exit_trap.store(true, Ordering::SeqCst);
532            }
533            // Execute code here...
534            if sig == SIGEXIT {
535                traps().in_exit_trap.store(false, Ordering::SeqCst);
536            }
537            traps().in_trap.store(false, Ordering::SeqCst);
538        }
539        TrapAction::Function(_name) => {
540            // Would call the function - needs executor integration
541            traps().in_trap.store(true, Ordering::SeqCst);
542            // Call function here...
543            traps().in_trap.store(false, Ordering::SeqCst);
544        }
545        TrapAction::Default => {}
546    }
547}
548
549/// Enable signal queueing
550pub fn queue_signals() {
551    SIGNAL_QUEUE.enable();
552}
553
554/// Disable signal queueing and process queued signals
555pub fn unqueue_signals() {
556    SIGNAL_QUEUE.disable();
557    while let Some(sig) = SIGNAL_QUEUE.pop() {
558        handle_signal(sig);
559    }
560}
561
562/// Check if signal queueing is enabled
563pub fn queueing_enabled() -> bool {
564    SIGNAL_QUEUE.is_enabled()
565}
566
567/// Enable trap queueing
568pub fn queue_traps() {
569    TRAP_QUEUE.enable();
570}
571
572/// Disable trap queueing and run queued traps
573pub fn unqueue_traps() {
574    TRAP_QUEUE.disable();
575    while let Some(sig) = TRAP_QUEUE.pop() {
576        if let Some(action) = traps().get_trap(sig) {
577            run_trap(sig, &action);
578        }
579    }
580}
581
582/// Block a signal
583pub fn signal_block(sig: i32) {
584    unsafe {
585        let mut set: libc::sigset_t = std::mem::zeroed();
586        libc::sigemptyset(&mut set);
587        libc::sigaddset(&mut set, sig);
588        libc::sigprocmask(libc::SIG_BLOCK, &set, std::ptr::null_mut());
589    }
590}
591
592/// Unblock a signal
593pub fn signal_unblock(sig: i32) {
594    unsafe {
595        let mut set: libc::sigset_t = std::mem::zeroed();
596        libc::sigemptyset(&mut set);
597        libc::sigaddset(&mut set, sig);
598        libc::sigprocmask(libc::SIG_UNBLOCK, &set, std::ptr::null_mut());
599    }
600}
601
602/// Block SIGINT for interactive shells
603pub fn hold_intr() {
604    signal_block(libc::SIGINT);
605}
606
607/// Unblock SIGINT
608pub fn release_intr() {
609    signal_unblock(libc::SIGINT);
610}
611
612/// Install default interrupt handler for interactive shells
613pub fn setup_intr() {
614    unsafe {
615        libc::signal(libc::SIGINT, handler as *const () as usize);
616    }
617}
618
619/// Get last received signal
620pub fn last_signal() -> i32 {
621    LAST_SIGNAL.load(Ordering::SeqCst)
622}
623
624/// Kill a process group
625pub fn killpg(pgrp: i32, sig: i32) -> i32 {
626    unsafe { libc::killpg(pgrp, sig) }
627}
628
629/// Kill a process
630pub fn kill(pid: i32, sig: i32) -> i32 {
631    unsafe { libc::kill(pid, sig) }
632}
633
634/// Check and clear SIGCHLD flag.
635pub fn signal_check_sigchld() -> bool {
636    SIGCHLD_RECEIVED.swap(false, Ordering::SeqCst)
637}
638
639/// Check and clear SIGWINCH flag.
640pub fn signal_check_sigwinch() -> bool {
641    SIGWINCH_RECEIVED.swap(false, Ordering::SeqCst)
642}
643
644/// Clear the cancellation signal.
645pub fn signal_clear_cancel() {
646    LAST_SIGNAL.store(0, Ordering::SeqCst);
647}
648
649/// Check if a cancellation signal (SIGINT) was received.
650pub fn signal_check_cancel() -> i32 {
651    let sig = LAST_SIGNAL.load(Ordering::SeqCst);
652    if sig == libc::SIGINT {
653        sig
654    } else {
655        0
656    }
657}
658
659/// Set up signal handlers for the shell.
660pub fn signal_set_handlers(interactive: bool) {
661    MAIN_PID.store(getpid().as_raw(), Ordering::Relaxed);
662
663    // Ignore SIGPIPE - we handle broken pipes ourselves
664    let ignore = SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty());
665    unsafe {
666        let _ = nix::sys::signal::sigaction(NixSignal::SIGPIPE, &ignore);
667        let _ = nix::sys::signal::sigaction(NixSignal::SIGQUIT, &ignore);
668    }
669
670    // Set up our handler for key signals
671    let sa_handler = SigAction::new(
672        SigHandler::Handler(handler),
673        SaFlags::SA_RESTART,
674        SigSet::empty(),
675    );
676
677    unsafe {
678        let _ = nix::sys::signal::sigaction(NixSignal::SIGINT, &sa_handler);
679        let _ = nix::sys::signal::sigaction(NixSignal::SIGCHLD, &sa_handler);
680    }
681
682    if interactive {
683        // Ignore job control signals in interactive mode
684        unsafe {
685            let _ = nix::sys::signal::sigaction(NixSignal::SIGTSTP, &ignore);
686            let _ = nix::sys::signal::sigaction(NixSignal::SIGTTOU, &ignore);
687        }
688
689        // Handle SIGWINCH for terminal resize
690        unsafe {
691            let _ = nix::sys::signal::sigaction(NixSignal::SIGWINCH, &sa_handler);
692        }
693
694        // Handle SIGHUP and SIGTERM
695        unsafe {
696            let _ = nix::sys::signal::sigaction(NixSignal::SIGHUP, &sa_handler);
697            let _ = nix::sys::signal::sigaction(NixSignal::SIGTERM, &sa_handler);
698        }
699    }
700}
701
702/// Reset all signal handlers to default (called after fork).
703pub fn signal_reset_handlers() {
704    let default = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty());
705
706    let signals = [
707        NixSignal::SIGHUP,
708        NixSignal::SIGINT,
709        NixSignal::SIGQUIT,
710        NixSignal::SIGTERM,
711        NixSignal::SIGCHLD,
712        NixSignal::SIGTSTP,
713        NixSignal::SIGTTIN,
714        NixSignal::SIGTTOU,
715        NixSignal::SIGPIPE,
716    ];
717
718    for sig in signals {
719        unsafe {
720            let _ = nix::sys::signal::sigaction(sig, &default);
721        }
722    }
723}
724
725/// Unblock all signals.
726pub fn signal_unblock_all() {
727    let _ = sigprocmask(SigmaskHow::SIG_SETMASK, Some(&SigSet::empty()), None);
728}
729
730/// Block SIGCHLD temporarily.
731pub fn signal_block_sigchld() -> SigSet {
732    let mut mask = SigSet::empty();
733    mask.add(NixSignal::SIGCHLD);
734    let mut old = SigSet::empty();
735    let _ = sigprocmask(SigmaskHow::SIG_BLOCK, Some(&mask), Some(&mut old));
736    old
737}
738
739/// Restore previous signal mask.
740pub fn signal_restore_mask(mask: &SigSet) {
741    let _ = sigprocmask(SigmaskHow::SIG_SETMASK, Some(mask), None);
742}
743
744/// Get signal description from number.
745pub fn signal_desc(sig: i32) -> &'static str {
746    match sig {
747        s if s == libc::SIGHUP => "Hangup",
748        s if s == libc::SIGINT => "Interrupt",
749        s if s == libc::SIGQUIT => "Quit",
750        s if s == libc::SIGILL => "Illegal instruction",
751        s if s == libc::SIGTRAP => "Trace trap",
752        s if s == libc::SIGABRT => "Abort",
753        s if s == libc::SIGBUS => "Bus error",
754        s if s == libc::SIGFPE => "Floating point exception",
755        s if s == libc::SIGKILL => "Killed",
756        s if s == libc::SIGUSR1 => "User signal 1",
757        s if s == libc::SIGSEGV => "Segmentation fault",
758        s if s == libc::SIGUSR2 => "User signal 2",
759        s if s == libc::SIGPIPE => "Broken pipe",
760        s if s == libc::SIGALRM => "Alarm clock",
761        s if s == libc::SIGTERM => "Terminated",
762        s if s == libc::SIGCHLD => "Child status changed",
763        s if s == libc::SIGCONT => "Continued",
764        s if s == libc::SIGSTOP => "Stopped (signal)",
765        s if s == libc::SIGTSTP => "Stopped",
766        s if s == libc::SIGTTIN => "Stopped (tty input)",
767        s if s == libc::SIGTTOU => "Stopped (tty output)",
768        s if s == libc::SIGURG => "Urgent I/O condition",
769        s if s == libc::SIGXCPU => "CPU time limit exceeded",
770        s if s == libc::SIGXFSZ => "File size limit exceeded",
771        s if s == libc::SIGVTALRM => "Virtual timer expired",
772        s if s == libc::SIGPROF => "Profiling timer expired",
773        s if s == libc::SIGWINCH => "Window size changed",
774        s if s == libc::SIGIO => "I/O possible",
775        s if s == libc::SIGSYS => "Bad system call",
776        _ => "Unknown signal",
777    }
778}
779
780#[cfg(test)]
781mod tests {
782    use super::*;
783
784    #[test]
785    fn test_sig_by_name() {
786        assert_eq!(sig_by_name("INT"), Some(libc::SIGINT));
787        assert_eq!(sig_by_name("SIGINT"), Some(libc::SIGINT));
788        assert_eq!(sig_by_name("int"), Some(libc::SIGINT));
789        assert_eq!(sig_by_name("HUP"), Some(libc::SIGHUP));
790        assert_eq!(sig_by_name("TERM"), Some(libc::SIGTERM));
791        assert_eq!(sig_by_name("EXIT"), Some(SIGEXIT));
792        assert_eq!(sig_by_name("9"), Some(9));
793    }
794
795    #[test]
796    fn test_sig_name() {
797        assert_eq!(sig_name(libc::SIGINT), Some("INT"));
798        assert_eq!(sig_name(libc::SIGHUP), Some("HUP"));
799        assert_eq!(sig_name(SIGEXIT), Some("EXIT"));
800    }
801
802    #[test]
803    fn test_trap_handler() {
804        let handler = TrapHandler::new();
805
806        // Initially not trapped
807        assert!(!handler.is_trapped(libc::SIGUSR1));
808
809        // Set a trap
810        handler
811            .set_trap(libc::SIGUSR1, TrapAction::Code("echo trapped".to_string()))
812            .unwrap();
813        assert!(handler.is_trapped(libc::SIGUSR1));
814
815        // Unset trap
816        handler.unset_trap(libc::SIGUSR1);
817        assert!(!handler.is_trapped(libc::SIGUSR1));
818    }
819
820    #[test]
821    fn test_ignore_trap() {
822        let handler = TrapHandler::new();
823
824        handler.set_trap(libc::SIGUSR1, TrapAction::Ignore).unwrap();
825        assert!(handler.is_ignored(libc::SIGUSR1));
826        assert!(!handler.is_trapped(libc::SIGUSR1));
827    }
828
829    #[test]
830    fn test_signal_queue() {
831        // Enable queueing
832        queue_signals();
833        assert!(queueing_enabled());
834
835        // Disable queueing
836        unqueue_signals();
837        assert!(!queueing_enabled());
838    }
839
840    #[test]
841    fn test_cant_trap_sigkill() {
842        let handler = TrapHandler::new();
843        let result = handler.set_trap(libc::SIGKILL, TrapAction::Code("echo".to_string()));
844        assert!(result.is_err());
845    }
846}
847
848// ---------------------------------------------------------------------------
849// Missing functions from signals.c
850// ---------------------------------------------------------------------------
851
852/// Install a signal handler (from signals.c install_handler)
853#[cfg(unix)]
854pub fn install_handler(sig: i32) {
855    unsafe {
856        libc::signal(sig, handler_func as *const () as libc::sighandler_t);
857    }
858}
859
860#[cfg(unix)]
861extern "C" fn handler_func(sig: libc::c_int) {
862    // Re-install handler (for non-BSD systems)
863    unsafe {
864        libc::signal(sig, handler_func as *const () as libc::sighandler_t);
865    }
866    // Record that signal was received
867    LAST_SIGNAL.store(sig, std::sync::atomic::Ordering::Relaxed);
868}
869
870/// Number of signals (from signals.c SIGCOUNT)
871pub const SIGCOUNT: i32 = 32;
872
873/// Total trap count including EXIT and ERR
874pub const TRAPCOUNT: usize = (SIGCOUNT + 3) as usize;
875
876/// Check if a signal is fatal (can't be caught)
877pub fn is_fatal_signal(sig: i32) -> bool {
878    sig == libc::SIGKILL || sig == libc::SIGSTOP
879}
880
881/// Block all signals
882#[cfg(unix)]
883pub fn signal_block_all() {
884    unsafe {
885        let mut set: libc::sigset_t = std::mem::zeroed();
886        libc::sigfillset(&mut set);
887        libc::sigprocmask(libc::SIG_BLOCK, &set, std::ptr::null_mut());
888    }
889}
890
891/// Save signal mask (without the existing duplicate)
892#[cfg(unix)]
893pub fn signal_save_mask_raw() -> libc::sigset_t {
894    let mut old: libc::sigset_t = unsafe { std::mem::zeroed() };
895    unsafe {
896        libc::sigprocmask(libc::SIG_BLOCK, std::ptr::null(), &mut old);
897    }
898    old
899}
900
901/// Set up default signal handlers for the shell (from signals.c)
902#[cfg(unix)]
903pub fn signal_default_setup() {
904    unsafe {
905        // Ignore SIGQUIT and SIGPIPE by default in interactive shells
906        libc::signal(libc::SIGQUIT, libc::SIG_IGN);
907        libc::signal(libc::SIGPIPE, libc::SIG_IGN);
908
909        // Set up handler for SIGCHLD
910        install_handler(libc::SIGCHLD);
911
912        // Set up handler for SIGWINCH
913        install_handler(libc::SIGWINCH);
914
915        // Set up handler for SIGALRM
916        install_handler(libc::SIGALRM);
917    }
918}
919
920/// Suspend the current process (from signals.c)
921#[cfg(unix)]
922pub fn signal_suspend() {
923    unsafe {
924        libc::raise(libc::SIGTSTP);
925    }
926}
927
928/// Wait for a signal (from signals.c)
929#[cfg(unix)]
930pub fn signal_wait() -> i32 {
931    let mut set: libc::sigset_t = unsafe { std::mem::zeroed() };
932    let mut sig: libc::c_int = 0;
933    unsafe {
934        libc::sigemptyset(&mut set);
935        libc::sigwait(&set, &mut sig);
936    }
937    sig
938}
939
940/// Check if signal is pending
941#[cfg(unix)]
942pub fn signal_pending(sig: i32) -> bool {
943    unsafe {
944        let mut set: libc::sigset_t = std::mem::zeroed();
945        if libc::sigpending(&mut set) == 0 {
946            libc::sigismember(&set, sig) == 1
947        } else {
948            false
949        }
950    }
951}
952
953/// Scope-based trap management (from signals.c starttrapscope/endtrapscope)
954#[derive(Debug, Default)]
955pub struct TrapScope {
956    saved_traps: Vec<(i32, TrapAction)>,
957}
958
959impl TrapScope {
960    pub fn new() -> Self {
961        Self::default()
962    }
963
964    /// Save the current trap state for a signal
965    pub fn save(&mut self, sig: i32, action: TrapAction) {
966        self.saved_traps.push((sig, action));
967    }
968
969    /// Get saved traps to restore
970    pub fn saved(&self) -> &[(i32, TrapAction)] {
971        &self.saved_traps
972    }
973}
974
975/// Signal name list for display (from signals.c)
976pub fn signal_names_list() -> Vec<String> {
977    let mut names = Vec::with_capacity(SIGCOUNT as usize + 1);
978    names.push("EXIT".to_string());
979    for i in 1..=SIGCOUNT {
980        if let Some(name) = sig_name(i) {
981            names.push(name.to_string());
982        } else {
983            names.push(format!("SIG{}", i));
984        }
985    }
986    names
987}
988
989// ---------------------------------------------------------------------------
990// Remaining 18 missing signals.c functions
991// ---------------------------------------------------------------------------
992
993/// Disable interrupts (from signals.c nointr)
994#[cfg(unix)]
995pub fn nointr() {
996    unsafe {
997        libc::signal(libc::SIGINT, libc::SIG_IGN);
998    }
999}
1000
1001/// Hold interrupts (save and block) (from signals.c holdintr)
1002#[cfg(unix)]
1003pub fn holdintr() {
1004    signal_block(libc::SIGINT);
1005}
1006
1007/// Release held interrupts (from signals.c noholdintr)
1008#[cfg(unix)]
1009pub fn noholdintr() {
1010    signal_unblock(libc::SIGINT);
1011}
1012
1013/// Get current signal mask (from signals.c signal_mask)
1014#[cfg(unix)]
1015pub fn signal_mask(sig: i32) -> libc::sigset_t {
1016    let mut set: libc::sigset_t = unsafe { std::mem::zeroed() };
1017    unsafe {
1018        libc::sigemptyset(&mut set);
1019        libc::sigaddset(&mut set, sig);
1020    }
1021    set
1022}
1023
1024/// Set signal mask (from signals.c signal_setmask)
1025#[cfg(unix)]
1026pub fn signal_setmask(mask: &libc::sigset_t) {
1027    unsafe {
1028        libc::sigprocmask(libc::SIG_SETMASK, mask, std::ptr::null_mut());
1029    }
1030}
1031
1032/// Wait for child processes with signal handling (from signals.c wait_for_processes)
1033#[cfg(unix)]
1034pub fn wait_for_processes() -> Vec<(i32, i32)> {
1035    let mut results = Vec::new();
1036    loop {
1037        let mut status: i32 = 0;
1038        let pid = unsafe { libc::waitpid(-1, &mut status, libc::WNOHANG | libc::WUNTRACED) };
1039        if pid <= 0 {
1040            break;
1041        }
1042        results.push((pid, status));
1043    }
1044    results
1045}
1046
1047/// Main signal handler (from signals.c zhandler)
1048#[cfg(unix)]
1049extern "C" fn zhandler(sig: libc::c_int) {
1050    // Re-install the handler
1051    unsafe {
1052        libc::signal(sig, zhandler as *const () as libc::sighandler_t);
1053    }
1054    // Record signal
1055    LAST_SIGNAL.store(sig, std::sync::atomic::Ordering::Relaxed);
1056}
1057
1058/// Kill all running jobs (from signals.c killrunjobs)
1059#[cfg(unix)]
1060pub fn killrunjobs(sig: i32) {
1061    // This would need access to the job table
1062    // In practice, the exec module calls this during shutdown
1063    let _ = sig;
1064}
1065
1066/// Kill a specific job (from signals.c killjb)
1067#[cfg(unix)]
1068pub fn killjb(pgrp: i32, sig: i32) -> i32 {
1069    if pgrp > 0 {
1070        unsafe { libc::killpg(pgrp, sig) }
1071    } else {
1072        -1
1073    }
1074}
1075
1076/// Save trap state before function call (from signals.c dosavetrap)
1077pub fn dosavetrap(sig: i32, handler: &TrapHandler) -> Option<TrapAction> {
1078    handler.get_trap(sig)
1079}
1080
1081/// Set a trap (from signals.c settrap)
1082pub fn settrap(sig: i32, action: TrapAction) -> Result<(), String> {
1083    let handler = traps();
1084    handler.set_trap(sig, action)
1085}
1086
1087/// Unset a trap (from signals.c unsettrap)
1088pub fn unsettrap(sig: i32) {
1089    let handler = traps();
1090    handler.unset_trap(sig);
1091}
1092
1093/// Handle a pending trap (from signals.c handletrap)
1094pub fn handletrap(sig: i32) -> Option<String> {
1095    let handler = traps();
1096    if let Some(TrapAction::Code(code)) = handler.get_trap(sig) {
1097        Some(code)
1098    } else {
1099        None
1100    }
1101}
1102
1103/// Execute trap actions for pending signals (from signals.c dotrapargs)
1104pub fn dotrapargs(sig: i32, handler: &TrapHandler) -> Option<String> {
1105    match handler.get_trap(sig) {
1106        Some(TrapAction::Code(code)) => Some(code),
1107        _ => None,
1108    }
1109}
1110
1111/// Execute all pending traps (from signals.c dotrap)
1112pub fn dotrap(sig: i32) -> Option<String> {
1113    let handler = traps();
1114    dotrapargs(sig, handler)
1115}
1116
1117/// Remove a trap completely (from signals.c removetrap)
1118pub fn removetrap(sig: i32) {
1119    unsettrap(sig);
1120    // Also restore default handler
1121    #[cfg(unix)]
1122    unsafe {
1123        libc::signal(sig, libc::SIG_DFL);
1124    }
1125}
1126
1127/// Get realtime signal number (from signals.c rtsigno)
1128///
1129/// SIGRTMIN is typically 34 on Linux, not available on macOS
1130pub fn rtsigno(offset: i32) -> Option<i32> {
1131    #[cfg(target_os = "linux")]
1132    {
1133        // SIGRTMIN is 34 on most Linux systems
1134        let sigrtmin = 34;
1135        let sigrtmax = 64;
1136        let sig = sigrtmin + offset;
1137        if sig <= sigrtmax {
1138            Some(sig)
1139        } else {
1140            None
1141        }
1142    }
1143    #[cfg(not(target_os = "linux"))]
1144    {
1145        let _ = offset;
1146        None
1147    }
1148}
1149
1150/// Get realtime signal name (from signals.c rtsigname)
1151pub fn rtsigname(sig: i32) -> String {
1152    #[cfg(target_os = "linux")]
1153    {
1154        let sigrtmin = 34;
1155        let offset = sig - sigrtmin;
1156        if offset == 0 {
1157            "RTMIN".to_string()
1158        } else if offset > 0 {
1159            format!("RTMIN+{}", offset)
1160        } else {
1161            format!("SIG{}", sig)
1162        }
1163    }
1164    #[cfg(not(target_os = "linux"))]
1165    {
1166        format!("SIG{}", sig)
1167    }
1168}