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/// Check if we're in a forked child and re-raise signal if so.
390fn reraise_if_forked_child(sig: i32) -> bool {
391    if getpid().as_raw() == MAIN_PID.load(Ordering::Relaxed) {
392        return false;
393    }
394    unsafe {
395        libc::signal(sig, libc::SIG_DFL);
396        libc::raise(sig);
397    }
398    true
399}
400
401/// Signal handler function
402extern "C" fn handler(sig: i32) {
403    // Preserve errno
404    #[cfg(target_os = "macos")]
405    let saved_errno = unsafe { *libc::__error() };
406    #[cfg(not(target_os = "macos"))]
407    let saved_errno = unsafe { *libc::__errno_location() };
408
409    // Check if we're a forked child
410    if reraise_if_forked_child(sig) {
411        #[cfg(target_os = "macos")]
412        unsafe {
413            *libc::__error() = saved_errno
414        };
415        #[cfg(not(target_os = "macos"))]
416        unsafe {
417            *libc::__errno_location() = saved_errno
418        };
419        return;
420    }
421
422    LAST_SIGNAL.store(sig, Ordering::SeqCst);
423
424    // Track specific signals
425    if sig == libc::SIGCHLD {
426        SIGCHLD_RECEIVED.store(true, Ordering::SeqCst);
427    } else if sig == libc::SIGWINCH {
428        SIGWINCH_RECEIVED.store(true, Ordering::SeqCst);
429    }
430
431    // If queueing is enabled, queue the signal
432    if SIGNAL_QUEUE.is_enabled() {
433        SIGNAL_QUEUE.push(sig);
434        #[cfg(target_os = "macos")]
435        unsafe {
436            *libc::__error() = saved_errno
437        };
438        #[cfg(not(target_os = "macos"))]
439        unsafe {
440            *libc::__errno_location() = saved_errno
441        };
442        return;
443    }
444
445    // Handle the signal directly
446    handle_signal(sig);
447
448    #[cfg(target_os = "macos")]
449    unsafe {
450        *libc::__error() = saved_errno
451    };
452    #[cfg(not(target_os = "macos"))]
453    unsafe {
454        *libc::__errno_location() = saved_errno
455    };
456}
457
458/// Handle a signal
459fn handle_signal(sig: i32) {
460    match sig {
461        s if s == libc::SIGCHLD => {
462            // Child process status change - handled by job control
463        }
464        s if s == libc::SIGINT => {
465            // Interrupt - set error flag
466            if let Some(action) = traps().get_trap(s) {
467                run_trap(s, &action);
468            }
469        }
470        s if s == libc::SIGHUP => {
471            // Hangup
472            if let Some(action) = traps().get_trap(s) {
473                run_trap(s, &action);
474            }
475        }
476        s if s == libc::SIGWINCH => {
477            // Window size change
478            if let Some(action) = traps().get_trap(s) {
479                run_trap(s, &action);
480            }
481        }
482        s if s == libc::SIGALRM => {
483            // Alarm
484            if let Some(action) = traps().get_trap(s) {
485                run_trap(s, &action);
486            }
487        }
488        s if s == libc::SIGPIPE => {
489            // Broken pipe
490            if let Some(action) = traps().get_trap(s) {
491                run_trap(s, &action);
492            }
493        }
494        _ => {
495            // Other signals
496            if let Some(action) = traps().get_trap(sig) {
497                run_trap(sig, &action);
498            }
499        }
500    }
501}
502
503/// Run a trap action
504fn run_trap(sig: i32, action: &TrapAction) {
505    match action {
506        TrapAction::Ignore => {}
507        TrapAction::Code(_code) => {
508            // Would execute the code - needs executor integration
509            traps().in_trap.store(true, Ordering::SeqCst);
510            if sig == SIGEXIT {
511                traps().in_exit_trap.store(true, Ordering::SeqCst);
512            }
513            // Execute code here...
514            if sig == SIGEXIT {
515                traps().in_exit_trap.store(false, Ordering::SeqCst);
516            }
517            traps().in_trap.store(false, Ordering::SeqCst);
518        }
519        TrapAction::Function(_name) => {
520            // Would call the function - needs executor integration
521            traps().in_trap.store(true, Ordering::SeqCst);
522            // Call function here...
523            traps().in_trap.store(false, Ordering::SeqCst);
524        }
525        TrapAction::Default => {}
526    }
527}
528
529/// Enable signal queueing
530pub fn queue_signals() {
531    SIGNAL_QUEUE.enable();
532}
533
534/// Disable signal queueing and process queued signals
535pub fn unqueue_signals() {
536    SIGNAL_QUEUE.disable();
537    while let Some(sig) = SIGNAL_QUEUE.pop() {
538        handle_signal(sig);
539    }
540}
541
542/// Check if signal queueing is enabled
543pub fn queueing_enabled() -> bool {
544    SIGNAL_QUEUE.is_enabled()
545}
546
547/// Enable trap queueing
548pub fn queue_traps() {
549    TRAP_QUEUE.enable();
550}
551
552/// Disable trap queueing and run queued traps
553pub fn unqueue_traps() {
554    TRAP_QUEUE.disable();
555    while let Some(sig) = TRAP_QUEUE.pop() {
556        if let Some(action) = traps().get_trap(sig) {
557            run_trap(sig, &action);
558        }
559    }
560}
561
562/// Block a signal
563pub fn signal_block(sig: i32) {
564    unsafe {
565        let mut set: libc::sigset_t = std::mem::zeroed();
566        libc::sigemptyset(&mut set);
567        libc::sigaddset(&mut set, sig);
568        libc::sigprocmask(libc::SIG_BLOCK, &set, std::ptr::null_mut());
569    }
570}
571
572/// Unblock a signal
573pub fn signal_unblock(sig: i32) {
574    unsafe {
575        let mut set: libc::sigset_t = std::mem::zeroed();
576        libc::sigemptyset(&mut set);
577        libc::sigaddset(&mut set, sig);
578        libc::sigprocmask(libc::SIG_UNBLOCK, &set, std::ptr::null_mut());
579    }
580}
581
582/// Block SIGINT for interactive shells
583pub fn hold_intr() {
584    signal_block(libc::SIGINT);
585}
586
587/// Unblock SIGINT
588pub fn release_intr() {
589    signal_unblock(libc::SIGINT);
590}
591
592/// Install default interrupt handler for interactive shells
593pub fn setup_intr() {
594    unsafe {
595        libc::signal(libc::SIGINT, handler as *const () as usize);
596    }
597}
598
599/// Get last received signal
600pub fn last_signal() -> i32 {
601    LAST_SIGNAL.load(Ordering::SeqCst)
602}
603
604/// Kill a process group
605pub fn killpg(pgrp: i32, sig: i32) -> i32 {
606    unsafe { libc::killpg(pgrp, sig) }
607}
608
609/// Kill a process
610pub fn kill(pid: i32, sig: i32) -> i32 {
611    unsafe { libc::kill(pid, sig) }
612}
613
614/// Check and clear SIGCHLD flag.
615pub fn signal_check_sigchld() -> bool {
616    SIGCHLD_RECEIVED.swap(false, Ordering::SeqCst)
617}
618
619/// Check and clear SIGWINCH flag.
620pub fn signal_check_sigwinch() -> bool {
621    SIGWINCH_RECEIVED.swap(false, Ordering::SeqCst)
622}
623
624/// Clear the cancellation signal.
625pub fn signal_clear_cancel() {
626    LAST_SIGNAL.store(0, Ordering::SeqCst);
627}
628
629/// Check if a cancellation signal (SIGINT) was received.
630pub fn signal_check_cancel() -> i32 {
631    let sig = LAST_SIGNAL.load(Ordering::SeqCst);
632    if sig == libc::SIGINT {
633        sig
634    } else {
635        0
636    }
637}
638
639/// Set up signal handlers for the shell.
640pub fn signal_set_handlers(interactive: bool) {
641    MAIN_PID.store(getpid().as_raw(), Ordering::Relaxed);
642
643    // Ignore SIGPIPE - we handle broken pipes ourselves
644    let ignore = SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty());
645    unsafe {
646        let _ = nix::sys::signal::sigaction(NixSignal::SIGPIPE, &ignore);
647        let _ = nix::sys::signal::sigaction(NixSignal::SIGQUIT, &ignore);
648    }
649
650    // Set up our handler for key signals
651    let sa_handler = SigAction::new(
652        SigHandler::Handler(handler),
653        SaFlags::SA_RESTART,
654        SigSet::empty(),
655    );
656
657    unsafe {
658        let _ = nix::sys::signal::sigaction(NixSignal::SIGINT, &sa_handler);
659        let _ = nix::sys::signal::sigaction(NixSignal::SIGCHLD, &sa_handler);
660    }
661
662    if interactive {
663        // Ignore job control signals in interactive mode
664        unsafe {
665            let _ = nix::sys::signal::sigaction(NixSignal::SIGTSTP, &ignore);
666            let _ = nix::sys::signal::sigaction(NixSignal::SIGTTOU, &ignore);
667        }
668
669        // Handle SIGWINCH for terminal resize
670        unsafe {
671            let _ = nix::sys::signal::sigaction(NixSignal::SIGWINCH, &sa_handler);
672        }
673
674        // Handle SIGHUP and SIGTERM
675        unsafe {
676            let _ = nix::sys::signal::sigaction(NixSignal::SIGHUP, &sa_handler);
677            let _ = nix::sys::signal::sigaction(NixSignal::SIGTERM, &sa_handler);
678        }
679    }
680}
681
682/// Reset all signal handlers to default (called after fork).
683pub fn signal_reset_handlers() {
684    let default = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty());
685
686    let signals = [
687        NixSignal::SIGHUP,
688        NixSignal::SIGINT,
689        NixSignal::SIGQUIT,
690        NixSignal::SIGTERM,
691        NixSignal::SIGCHLD,
692        NixSignal::SIGTSTP,
693        NixSignal::SIGTTIN,
694        NixSignal::SIGTTOU,
695        NixSignal::SIGPIPE,
696    ];
697
698    for sig in signals {
699        unsafe {
700            let _ = nix::sys::signal::sigaction(sig, &default);
701        }
702    }
703}
704
705/// Unblock all signals.
706pub fn signal_unblock_all() {
707    let _ = sigprocmask(SigmaskHow::SIG_SETMASK, Some(&SigSet::empty()), None);
708}
709
710/// Block SIGCHLD temporarily.
711pub fn signal_block_sigchld() -> SigSet {
712    let mut mask = SigSet::empty();
713    mask.add(NixSignal::SIGCHLD);
714    let mut old = SigSet::empty();
715    let _ = sigprocmask(SigmaskHow::SIG_BLOCK, Some(&mask), Some(&mut old));
716    old
717}
718
719/// Restore previous signal mask.
720pub fn signal_restore_mask(mask: &SigSet) {
721    let _ = sigprocmask(SigmaskHow::SIG_SETMASK, Some(mask), None);
722}
723
724/// Get signal description from number.
725pub fn signal_desc(sig: i32) -> &'static str {
726    match sig {
727        s if s == libc::SIGHUP => "Hangup",
728        s if s == libc::SIGINT => "Interrupt",
729        s if s == libc::SIGQUIT => "Quit",
730        s if s == libc::SIGILL => "Illegal instruction",
731        s if s == libc::SIGTRAP => "Trace trap",
732        s if s == libc::SIGABRT => "Abort",
733        s if s == libc::SIGBUS => "Bus error",
734        s if s == libc::SIGFPE => "Floating point exception",
735        s if s == libc::SIGKILL => "Killed",
736        s if s == libc::SIGUSR1 => "User signal 1",
737        s if s == libc::SIGSEGV => "Segmentation fault",
738        s if s == libc::SIGUSR2 => "User signal 2",
739        s if s == libc::SIGPIPE => "Broken pipe",
740        s if s == libc::SIGALRM => "Alarm clock",
741        s if s == libc::SIGTERM => "Terminated",
742        s if s == libc::SIGCHLD => "Child status changed",
743        s if s == libc::SIGCONT => "Continued",
744        s if s == libc::SIGSTOP => "Stopped (signal)",
745        s if s == libc::SIGTSTP => "Stopped",
746        s if s == libc::SIGTTIN => "Stopped (tty input)",
747        s if s == libc::SIGTTOU => "Stopped (tty output)",
748        s if s == libc::SIGURG => "Urgent I/O condition",
749        s if s == libc::SIGXCPU => "CPU time limit exceeded",
750        s if s == libc::SIGXFSZ => "File size limit exceeded",
751        s if s == libc::SIGVTALRM => "Virtual timer expired",
752        s if s == libc::SIGPROF => "Profiling timer expired",
753        s if s == libc::SIGWINCH => "Window size changed",
754        s if s == libc::SIGIO => "I/O possible",
755        s if s == libc::SIGSYS => "Bad system call",
756        _ => "Unknown signal",
757    }
758}
759
760#[cfg(test)]
761mod tests {
762    use super::*;
763
764    #[test]
765    fn test_sig_by_name() {
766        assert_eq!(sig_by_name("INT"), Some(libc::SIGINT));
767        assert_eq!(sig_by_name("SIGINT"), Some(libc::SIGINT));
768        assert_eq!(sig_by_name("int"), Some(libc::SIGINT));
769        assert_eq!(sig_by_name("HUP"), Some(libc::SIGHUP));
770        assert_eq!(sig_by_name("TERM"), Some(libc::SIGTERM));
771        assert_eq!(sig_by_name("EXIT"), Some(SIGEXIT));
772        assert_eq!(sig_by_name("9"), Some(9));
773    }
774
775    #[test]
776    fn test_sig_name() {
777        assert_eq!(sig_name(libc::SIGINT), Some("INT"));
778        assert_eq!(sig_name(libc::SIGHUP), Some("HUP"));
779        assert_eq!(sig_name(SIGEXIT), Some("EXIT"));
780    }
781
782    #[test]
783    fn test_trap_handler() {
784        let handler = TrapHandler::new();
785
786        // Initially not trapped
787        assert!(!handler.is_trapped(libc::SIGUSR1));
788
789        // Set a trap
790        handler
791            .set_trap(libc::SIGUSR1, TrapAction::Code("echo trapped".to_string()))
792            .unwrap();
793        assert!(handler.is_trapped(libc::SIGUSR1));
794
795        // Unset trap
796        handler.unset_trap(libc::SIGUSR1);
797        assert!(!handler.is_trapped(libc::SIGUSR1));
798    }
799
800    #[test]
801    fn test_ignore_trap() {
802        let handler = TrapHandler::new();
803
804        handler.set_trap(libc::SIGUSR1, TrapAction::Ignore).unwrap();
805        assert!(handler.is_ignored(libc::SIGUSR1));
806        assert!(!handler.is_trapped(libc::SIGUSR1));
807    }
808
809    #[test]
810    fn test_signal_queue() {
811        // Enable queueing
812        queue_signals();
813        assert!(queueing_enabled());
814
815        // Disable queueing
816        unqueue_signals();
817        assert!(!queueing_enabled());
818    }
819
820    #[test]
821    fn test_cant_trap_sigkill() {
822        let handler = TrapHandler::new();
823        let result = handler.set_trap(libc::SIGKILL, TrapAction::Code("echo".to_string()));
824        assert!(result.is_err());
825    }
826}
827
828// ---------------------------------------------------------------------------
829// Missing functions from signals.c
830// ---------------------------------------------------------------------------
831
832/// Install a signal handler (from signals.c install_handler)
833#[cfg(unix)]
834pub fn install_handler(sig: i32) {
835    unsafe {
836        libc::signal(sig, handler_func as *const () as libc::sighandler_t);
837    }
838}
839
840#[cfg(unix)]
841extern "C" fn handler_func(sig: libc::c_int) {
842    // Re-install handler (for non-BSD systems)
843    unsafe {
844        libc::signal(sig, handler_func as *const () as libc::sighandler_t);
845    }
846    // Record that signal was received
847    LAST_SIGNAL.store(sig, std::sync::atomic::Ordering::Relaxed);
848}
849
850/// Number of signals (from signals.c SIGCOUNT)
851pub const SIGCOUNT: i32 = 32;
852
853/// Total trap count including EXIT and ERR
854pub const TRAPCOUNT: usize = (SIGCOUNT + 3) as usize;
855
856/// Check if a signal is fatal (can't be caught)
857pub fn is_fatal_signal(sig: i32) -> bool {
858    sig == libc::SIGKILL || sig == libc::SIGSTOP
859}
860
861/// Block all signals
862#[cfg(unix)]
863pub fn signal_block_all() {
864    unsafe {
865        let mut set: libc::sigset_t = std::mem::zeroed();
866        libc::sigfillset(&mut set);
867        libc::sigprocmask(libc::SIG_BLOCK, &set, std::ptr::null_mut());
868    }
869}
870
871/// Save signal mask (without the existing duplicate)
872#[cfg(unix)]
873pub fn signal_save_mask_raw() -> libc::sigset_t {
874    let mut old: libc::sigset_t = unsafe { std::mem::zeroed() };
875    unsafe {
876        libc::sigprocmask(libc::SIG_BLOCK, std::ptr::null(), &mut old);
877    }
878    old
879}
880
881/// Set up default signal handlers for the shell (from signals.c)
882#[cfg(unix)]
883pub fn signal_default_setup() {
884    unsafe {
885        // Ignore SIGQUIT and SIGPIPE by default in interactive shells
886        libc::signal(libc::SIGQUIT, libc::SIG_IGN);
887        libc::signal(libc::SIGPIPE, libc::SIG_IGN);
888
889        // Set up handler for SIGCHLD
890        install_handler(libc::SIGCHLD);
891
892        // Set up handler for SIGWINCH
893        install_handler(libc::SIGWINCH);
894
895        // Set up handler for SIGALRM
896        install_handler(libc::SIGALRM);
897    }
898}
899
900/// Suspend the current process (from signals.c)
901#[cfg(unix)]
902pub fn signal_suspend() {
903    unsafe {
904        libc::raise(libc::SIGTSTP);
905    }
906}
907
908/// Wait for a signal (from signals.c)
909#[cfg(unix)]
910pub fn signal_wait() -> i32 {
911    let mut set: libc::sigset_t = unsafe { std::mem::zeroed() };
912    let mut sig: libc::c_int = 0;
913    unsafe {
914        libc::sigemptyset(&mut set);
915        libc::sigwait(&set, &mut sig);
916    }
917    sig
918}
919
920/// Check if signal is pending
921#[cfg(unix)]
922pub fn signal_pending(sig: i32) -> bool {
923    unsafe {
924        let mut set: libc::sigset_t = std::mem::zeroed();
925        if libc::sigpending(&mut set) == 0 {
926            libc::sigismember(&set, sig) == 1
927        } else {
928            false
929        }
930    }
931}
932
933/// Scope-based trap management (from signals.c starttrapscope/endtrapscope)
934#[derive(Debug, Default)]
935pub struct TrapScope {
936    saved_traps: Vec<(i32, TrapAction)>,
937}
938
939impl TrapScope {
940    pub fn new() -> Self {
941        Self::default()
942    }
943
944    /// Save the current trap state for a signal
945    pub fn save(&mut self, sig: i32, action: TrapAction) {
946        self.saved_traps.push((sig, action));
947    }
948
949    /// Get saved traps to restore
950    pub fn saved(&self) -> &[(i32, TrapAction)] {
951        &self.saved_traps
952    }
953}
954
955/// Signal name list for display (from signals.c)
956pub fn signal_names_list() -> Vec<String> {
957    let mut names = Vec::with_capacity(SIGCOUNT as usize + 1);
958    names.push("EXIT".to_string());
959    for i in 1..=SIGCOUNT {
960        if let Some(name) = sig_name(i) {
961            names.push(name.to_string());
962        } else {
963            names.push(format!("SIG{}", i));
964        }
965    }
966    names
967}
968
969// ---------------------------------------------------------------------------
970// Remaining 18 missing signals.c functions
971// ---------------------------------------------------------------------------
972
973/// Disable interrupts (from signals.c nointr)
974#[cfg(unix)]
975pub fn nointr() {
976    unsafe {
977        libc::signal(libc::SIGINT, libc::SIG_IGN);
978    }
979}
980
981/// Hold interrupts (save and block) (from signals.c holdintr)
982#[cfg(unix)]
983pub fn holdintr() {
984    signal_block(libc::SIGINT);
985}
986
987/// Release held interrupts (from signals.c noholdintr)
988#[cfg(unix)]
989pub fn noholdintr() {
990    signal_unblock(libc::SIGINT);
991}
992
993/// Get current signal mask (from signals.c signal_mask)
994#[cfg(unix)]
995pub fn signal_mask(sig: i32) -> libc::sigset_t {
996    let mut set: libc::sigset_t = unsafe { std::mem::zeroed() };
997    unsafe {
998        libc::sigemptyset(&mut set);
999        libc::sigaddset(&mut set, sig);
1000    }
1001    set
1002}
1003
1004/// Set signal mask (from signals.c signal_setmask)
1005#[cfg(unix)]
1006pub fn signal_setmask(mask: &libc::sigset_t) {
1007    unsafe {
1008        libc::sigprocmask(libc::SIG_SETMASK, mask, std::ptr::null_mut());
1009    }
1010}
1011
1012/// Wait for child processes with signal handling (from signals.c wait_for_processes)
1013#[cfg(unix)]
1014pub fn wait_for_processes() -> Vec<(i32, i32)> {
1015    let mut results = Vec::new();
1016    loop {
1017        let mut status: i32 = 0;
1018        let pid = unsafe { libc::waitpid(-1, &mut status, libc::WNOHANG | libc::WUNTRACED) };
1019        if pid <= 0 {
1020            break;
1021        }
1022        results.push((pid, status));
1023    }
1024    results
1025}
1026
1027/// Main signal handler (from signals.c zhandler)
1028#[cfg(unix)]
1029extern "C" fn zhandler(sig: libc::c_int) {
1030    // Re-install the handler
1031    unsafe {
1032        libc::signal(sig, zhandler as *const () as libc::sighandler_t);
1033    }
1034    // Record signal
1035    LAST_SIGNAL.store(sig, std::sync::atomic::Ordering::Relaxed);
1036}
1037
1038/// Kill all running jobs (from signals.c killrunjobs)
1039#[cfg(unix)]
1040pub fn killrunjobs(sig: i32) {
1041    // This would need access to the job table
1042    // In practice, the exec module calls this during shutdown
1043    let _ = sig;
1044}
1045
1046/// Kill a specific job (from signals.c killjb)
1047#[cfg(unix)]
1048pub fn killjb(pgrp: i32, sig: i32) -> i32 {
1049    if pgrp > 0 {
1050        unsafe { libc::killpg(pgrp, sig) }
1051    } else {
1052        -1
1053    }
1054}
1055
1056/// Save trap state before function call (from signals.c dosavetrap)
1057pub fn dosavetrap(sig: i32, handler: &TrapHandler) -> Option<TrapAction> {
1058    handler.get_trap(sig)
1059}
1060
1061/// Set a trap (from signals.c settrap)
1062pub fn settrap(sig: i32, action: TrapAction) -> Result<(), String> {
1063    let handler = traps();
1064    handler.set_trap(sig, action)
1065}
1066
1067/// Unset a trap (from signals.c unsettrap)
1068pub fn unsettrap(sig: i32) {
1069    let handler = traps();
1070    handler.unset_trap(sig);
1071}
1072
1073/// Handle a pending trap (from signals.c handletrap)
1074pub fn handletrap(sig: i32) -> Option<String> {
1075    let handler = traps();
1076    if let Some(TrapAction::Code(code)) = handler.get_trap(sig) {
1077        Some(code)
1078    } else {
1079        None
1080    }
1081}
1082
1083/// Execute trap actions for pending signals (from signals.c dotrapargs)
1084pub fn dotrapargs(sig: i32, handler: &TrapHandler) -> Option<String> {
1085    match handler.get_trap(sig) {
1086        Some(TrapAction::Code(code)) => Some(code),
1087        _ => None,
1088    }
1089}
1090
1091/// Execute all pending traps (from signals.c dotrap)
1092pub fn dotrap(sig: i32) -> Option<String> {
1093    let handler = traps();
1094    dotrapargs(sig, handler)
1095}
1096
1097/// Remove a trap completely (from signals.c removetrap)
1098pub fn removetrap(sig: i32) {
1099    unsettrap(sig);
1100    // Also restore default handler
1101    #[cfg(unix)]
1102    unsafe {
1103        libc::signal(sig, libc::SIG_DFL);
1104    }
1105}
1106
1107/// Get realtime signal number (from signals.c rtsigno)
1108///
1109/// SIGRTMIN is typically 34 on Linux, not available on macOS
1110pub fn rtsigno(offset: i32) -> Option<i32> {
1111    #[cfg(target_os = "linux")]
1112    {
1113        // SIGRTMIN is 34 on most Linux systems
1114        let sigrtmin = 34;
1115        let sigrtmax = 64;
1116        let sig = sigrtmin + offset;
1117        if sig <= sigrtmax {
1118            Some(sig)
1119        } else {
1120            None
1121        }
1122    }
1123    #[cfg(not(target_os = "linux"))]
1124    {
1125        let _ = offset;
1126        None
1127    }
1128}
1129
1130/// Get realtime signal name (from signals.c rtsigname)
1131pub fn rtsigname(sig: i32) -> String {
1132    #[cfg(target_os = "linux")]
1133    {
1134        let sigrtmin = 34;
1135        let offset = sig - sigrtmin;
1136        if offset == 0 {
1137            "RTMIN".to_string()
1138        } else if offset > 0 {
1139            format!("RTMIN+{}", offset)
1140        } else {
1141            format!("SIG{}", sig)
1142        }
1143    }
1144    #[cfg(not(target_os = "linux"))]
1145    {
1146        format!("SIG{}", sig)
1147    }
1148}