Skip to main content

strat9_kernel/process/
signal.rs

1//! Signal handling for Strat9-OS.
2//!
3//! Provides basic signal infrastructure for POSIX compatibility.
4//! Implements signal delivery, masking, and handling.
5
6use core::sync::atomic::{AtomicU64, Ordering};
7
8/// Signal numbers (POSIX-compatible).
9///
10/// Standard POSIX signal numbers for compatibility with userspace libc.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u32)]
13pub enum Signal {
14    /// Hangup detected on controlling terminal
15    SIGHUP = 1,
16    /// Interrupt from keyboard (Ctrl+C)
17    SIGINT = 2,
18    /// Quit from keyboard (Ctrl+\)
19    SIGQUIT = 3,
20    /// Illegal instruction
21    SIGILL = 4,
22    /// Trace/breakpoint trap
23    SIGTRAP = 5,
24    /// Abort signal
25    SIGABRT = 6,
26    /// Bus error (bad memory access)
27    SIGBUS = 7,
28    /// Floating-point exception
29    SIGFPE = 8,
30    /// Kill signal (cannot be caught or ignored)
31    SIGKILL = 9,
32    /// User-defined signal 1
33    SIGUSR1 = 10,
34    /// Segmentation fault
35    SIGSEGV = 11,
36    /// User-defined signal 2
37    SIGUSR2 = 12,
38    /// Broken pipe
39    SIGPIPE = 13,
40    /// Timer signal
41    SIGALRM = 14,
42    /// Termination signal
43    SIGTERM = 15,
44    /// Child stopped or terminated
45    SIGCHLD = 17,
46    /// Continue if stopped
47    SIGCONT = 18,
48    /// Stop process (cannot be caught or ignored)
49    SIGSTOP = 19,
50    /// Stop typed at terminal
51    SIGTSTP = 20,
52    /// Background read attempt
53    SIGTTIN = 21,
54    /// Background write attempt
55    SIGTTOU = 22,
56    /// Urgent data on socket
57    SIGURG = 23,
58    /// CPU time limit exceeded
59    SIGXCPU = 24,
60    /// File size limit exceeded
61    SIGXFSZ = 25,
62    /// Virtual timer expired
63    SIGVTALRM = 26,
64    /// Profiling timer expired
65    SIGPROF = 27,
66    /// Window size changed
67    SIGWINCH = 28,
68    /// I/O possible on socket
69    SIGIO = 29,
70    /// Power failure
71    SIGPWR = 30,
72    /// Bad system call
73    SIGSYS = 31,
74}
75
76impl Signal {
77    /// Convert a signal number to a Signal enum.
78    pub fn from_u32(num: u32) -> Option<Self> {
79        match num {
80            1 => Some(Signal::SIGHUP),
81            2 => Some(Signal::SIGINT),
82            3 => Some(Signal::SIGQUIT),
83            4 => Some(Signal::SIGILL),
84            5 => Some(Signal::SIGTRAP),
85            6 => Some(Signal::SIGABRT),
86            7 => Some(Signal::SIGBUS),
87            8 => Some(Signal::SIGFPE),
88            9 => Some(Signal::SIGKILL),
89            10 => Some(Signal::SIGUSR1),
90            11 => Some(Signal::SIGSEGV),
91            12 => Some(Signal::SIGUSR2),
92            13 => Some(Signal::SIGPIPE),
93            14 => Some(Signal::SIGALRM),
94            15 => Some(Signal::SIGTERM),
95            17 => Some(Signal::SIGCHLD),
96            18 => Some(Signal::SIGCONT),
97            19 => Some(Signal::SIGSTOP),
98            20 => Some(Signal::SIGTSTP),
99            21 => Some(Signal::SIGTTIN),
100            22 => Some(Signal::SIGTTOU),
101            23 => Some(Signal::SIGURG),
102            24 => Some(Signal::SIGXCPU),
103            25 => Some(Signal::SIGXFSZ),
104            26 => Some(Signal::SIGVTALRM),
105            27 => Some(Signal::SIGPROF),
106            28 => Some(Signal::SIGWINCH),
107            29 => Some(Signal::SIGIO),
108            30 => Some(Signal::SIGPWR),
109            31 => Some(Signal::SIGSYS),
110            _ => None,
111        }
112    }
113
114    /// Convert Signal to its numeric value.
115    pub fn as_u32(self) -> u32 {
116        self as u32
117    }
118
119    /// Check if this signal cannot be caught or blocked.
120    pub fn is_uncatchable(self) -> bool {
121        matches!(self, Signal::SIGKILL | Signal::SIGSTOP)
122    }
123
124    /// Get the bit position for this signal in a signal mask.
125    pub fn bit(self) -> u64 {
126        1u64 << (self.as_u32() - 1)
127    }
128}
129
130/// Signal mask constants
131pub const SIGNAL_MASK_SIZE: usize = 8; // u64 = 64 bits, enough for signals 1-64
132pub const SIGNAL_MAX: u32 = 64;
133
134/// How to modify the signal mask
135pub const SIG_BLOCK: i32 = 0;
136pub const SIG_UNBLOCK: i32 = 1;
137pub const SIG_SETMASK: i32 = 2;
138
139/// A set of signals represented as a bitmask.
140///
141/// Uses atomic operations for lock-free signal delivery.
142#[derive(Debug)]
143pub struct SignalSet {
144    mask: AtomicU64,
145}
146
147impl Clone for SignalSet {
148    /// Performs the clone operation.
149    fn clone(&self) -> Self {
150        Self::from_mask(self.get_mask())
151    }
152}
153
154impl SignalSet {
155    /// Create an empty signal set.
156    pub const fn new() -> Self {
157        Self {
158            mask: AtomicU64::new(0),
159        }
160    }
161
162    /// Create a signal set from a raw mask value.
163    pub const fn from_mask(mask: u64) -> Self {
164        Self {
165            mask: AtomicU64::new(mask),
166        }
167    }
168
169    /// Add a signal to the set.
170    pub fn add(&self, signal: Signal) {
171        let bit = signal.bit();
172        self.mask.fetch_or(bit, Ordering::Release);
173    }
174
175    /// Remove a signal from the set.
176    pub fn remove(&self, signal: Signal) {
177        let bit = !signal.bit();
178        self.mask.fetch_and(bit, Ordering::AcqRel);
179    }
180
181    /// Check if a signal is in the set.
182    pub fn contains(&self, signal: Signal) -> bool {
183        let bit = signal.bit();
184        (self.mask.load(Ordering::Acquire) & bit) != 0
185    }
186
187    /// Check if the set is empty.
188    pub fn is_empty(&self) -> bool {
189        self.mask.load(Ordering::Acquire) == 0
190    }
191
192    /// Get the raw mask value.
193    pub fn get_mask(&self) -> u64 {
194        self.mask.load(Ordering::Acquire)
195    }
196
197    /// Set the raw mask value.
198    pub fn set_mask(&self, mask: u64) {
199        self.mask.store(mask, Ordering::Release);
200    }
201
202    /// Clear all signals.
203    pub fn clear(&self) {
204        self.mask.store(0, Ordering::Release);
205    }
206
207    /// Get the next pending signal (lowest numbered).
208    pub fn next_pending(&self) -> Option<Signal> {
209        let pending = self.mask.load(Ordering::Acquire);
210        if pending == 0 {
211            return None;
212        }
213        let signal_num = pending.trailing_zeros() + 1;
214        Signal::from_u32(signal_num)
215    }
216
217    /// Get signals that are in self but not in blocked.
218    pub fn unblocked(&self, blocked: &SignalSet) -> u64 {
219        let pending = self.mask.load(Ordering::Acquire);
220        let blocked_mask = blocked.mask.load(Ordering::Acquire);
221        pending & !blocked_mask
222    }
223
224    /// Atomically consume one pending unblocked signal (CAS loop).
225    pub fn consume_one_unblocked(&self, blocked: &SignalSet) -> Option<Signal> {
226        loop {
227            let pending = self.mask.load(Ordering::Acquire);
228            let blocked_mask = blocked.mask.load(Ordering::Acquire);
229            let unblocked = pending & !blocked_mask;
230            if unblocked == 0 {
231                return None;
232            }
233            let lowest_bit = unblocked & unblocked.wrapping_neg();
234            let new_pending = pending & !lowest_bit;
235            match self.mask.compare_exchange_weak(
236                pending,
237                new_pending,
238                Ordering::AcqRel,
239                Ordering::Acquire,
240            ) {
241                Ok(_) => {
242                    let signal_num = lowest_bit.trailing_zeros() + 1;
243                    return Signal::from_u32(signal_num);
244                }
245                Err(_) => continue,
246            }
247        }
248    }
249
250    /// Peek the next pending unblocked signal without consuming it.
251    pub fn peek_one_unblocked(&self, blocked: &SignalSet) -> Option<Signal> {
252        let pending = self.mask.load(Ordering::Acquire);
253        let blocked_mask = blocked.mask.load(Ordering::Acquire);
254        let unblocked = pending & !blocked_mask;
255        if unblocked == 0 {
256            return None;
257        }
258        let signal_num = unblocked.trailing_zeros() + 1;
259        Signal::from_u32(signal_num)
260    }
261
262    /// Try to consume a specific pending signal.
263    pub fn try_consume(&self, signal: Signal) -> bool {
264        let bit = signal.bit();
265        loop {
266            let pending = self.mask.load(Ordering::Acquire);
267            if (pending & bit) == 0 {
268                return false;
269            }
270            let new_pending = pending & !bit;
271            match self.mask.compare_exchange_weak(
272                pending,
273                new_pending,
274                Ordering::AcqRel,
275                Ordering::Acquire,
276            ) {
277                Ok(_) => return true,
278                Err(_) => continue,
279            }
280        }
281    }
282}
283
284/// Signal action flags
285pub const SA_NOCLDSTOP: u32 = 1 << 0;
286pub const SA_NOCLDWAIT: u32 = 1 << 1;
287pub const SA_SIGINFO: u32 = 1 << 2;
288pub const SA_RESTORER: u32 = 1 << 3;
289pub const SA_ONSTACK: u32 = 1 << 4;
290pub const SA_RESTART: u32 = 1 << 5;
291pub const SA_NODEFER: u32 = 1 << 6;
292pub const SA_RESETHAND: u32 = 1 << 7;
293
294pub const SIG_DFL: u64 = 0;
295pub const SIG_IGN: u64 = 1;
296
297#[derive(Debug, Clone, Copy)]
298#[repr(C)]
299pub struct SigActionData {
300    pub handler: u64,
301    pub flags: u64,
302    pub restorer: u64,
303    pub mask: u64,
304}
305
306impl SigActionData {
307    /// Builds a default instance.
308    pub const fn default() -> Self {
309        Self {
310            handler: SIG_DFL,
311            flags: 0,
312            restorer: 0,
313            mask: 0,
314        }
315    }
316
317    /// Returns whether default.
318    pub fn is_default(&self) -> bool {
319        self.handler == SIG_DFL
320    }
321    /// Returns whether ignore.
322    pub fn is_ignore(&self) -> bool {
323        self.handler == SIG_IGN
324    }
325    /// Returns whether user handler.
326    pub fn is_user_handler(&self) -> bool {
327        self.handler > 1
328    }
329}
330
331impl Default for SigActionData {
332    /// Builds a default instance.
333    fn default() -> Self {
334        Self::default()
335    }
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq)]
339pub enum DefaultAction {
340    Term,
341    Core,
342    Stop,
343    Cont,
344    Ign,
345}
346
347impl Signal {
348    /// Performs the default action operation.
349    pub fn default_action(self) -> DefaultAction {
350        match self {
351            Signal::SIGHUP
352            | Signal::SIGINT
353            | Signal::SIGPIPE
354            | Signal::SIGALRM
355            | Signal::SIGTERM
356            | Signal::SIGUSR1
357            | Signal::SIGUSR2
358            | Signal::SIGPROF
359            | Signal::SIGVTALRM
360            | Signal::SIGIO
361            | Signal::SIGPWR
362            | Signal::SIGSYS => DefaultAction::Term,
363
364            Signal::SIGQUIT
365            | Signal::SIGILL
366            | Signal::SIGABRT
367            | Signal::SIGFPE
368            | Signal::SIGSEGV
369            | Signal::SIGBUS
370            | Signal::SIGTRAP
371            | Signal::SIGXCPU
372            | Signal::SIGXFSZ => DefaultAction::Core,
373
374            Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
375                DefaultAction::Stop
376            }
377
378            Signal::SIGCONT => DefaultAction::Cont,
379
380            Signal::SIGCHLD | Signal::SIGURG | Signal::SIGWINCH => DefaultAction::Ign,
381
382            Signal::SIGKILL => DefaultAction::Term,
383        }
384    }
385}
386
387pub const SIGNAL_FRAME_MAGIC: u64 = 0x5354_5239_5349_4700;
388
389#[derive(Debug, Clone, Copy)]
390#[repr(C)]
391pub struct SignalFrame {
392    pub restorer: u64,
393    pub signo: u64,
394    pub saved_mask: u64,
395    pub rip: u64,
396    pub rsp: u64,
397    pub rflags: u64,
398    pub rax: u64,
399    pub rcx: u64,
400    pub rdx: u64,
401    pub rbx: u64,
402    pub rbp: u64,
403    pub rsi: u64,
404    pub rdi: u64,
405    pub r8: u64,
406    pub r9: u64,
407    pub r10: u64,
408    pub r11: u64,
409    pub r12: u64,
410    pub r13: u64,
411    pub r14: u64,
412    pub r15: u64,
413    pub magic: u64,
414}
415
416#[derive(Clone, Copy, PartialEq, Eq)]
417enum SignalDeliveryMode {
418    Normal,
419    InterruptReturn,
420}
421
422/// Count of signals deferred on the interrupt-return path because their
423/// default action (Term/Core/Stop) or missing restorer requires a task-switch
424/// or kill, which is not safe from timer-IRQ context.  Exposed for diagnostics.
425pub static SIGNAL_IRQ_DEFERRED_COUNT: AtomicU64 = AtomicU64::new(0);
426
427fn deliver_pending_signal_inner(
428    frame: &mut crate::syscall::SyscallFrame,
429    mode: SignalDeliveryMode,
430) -> bool {
431    let task = match crate::process::current_task_clone() {
432        Some(t) => t,
433        None => return false,
434    };
435
436    if task.is_kernel() {
437        return false;
438    }
439
440    if mode == SignalDeliveryMode::InterruptReturn
441        && task.irq_signal_delivery_blocked.load(Ordering::Acquire)
442    {
443        return false;
444    }
445
446    if mode == SignalDeliveryMode::Normal {
447        task.irq_signal_delivery_blocked
448            .store(false, Ordering::Release);
449    }
450
451    // A task runs on exactly one CPU at a time, so there is no concurrent
452    // signal-consumption path executing on behalf of the same task. Signal
453    // senders may still add new bits concurrently, so try_consume remains the
454    // authority for actually claiming the signal selected by the peek.
455    let signal = match task
456        .pending_signals
457        .peek_one_unblocked(&task.blocked_signals)
458    {
459        Some(s) => s,
460        None => return false,
461    };
462
463    let action = unsafe {
464        let actions = &*task.process.signal_actions.get();
465        actions[signal.as_u32() as usize]
466    };
467
468    if action.is_ignore() {
469        debug_assert!(
470            task.pending_signals.try_consume(signal),
471            "signal vanished between peek and consume (ignore path)"
472        );
473        return true;
474    }
475
476    if action.is_default() {
477        match signal.default_action() {
478            DefaultAction::Ign | DefaultAction::Cont => {
479                debug_assert!(
480                    task.pending_signals.try_consume(signal),
481                    "signal vanished between peek and consume (ign/cont path)"
482                );
483                return true;
484            }
485            DefaultAction::Stop => {
486                if mode == SignalDeliveryMode::InterruptReturn {
487                    // Stop requires task suspension : not safe from IRQ context.
488                    // Leave the signal pending; the syscall-return path will deliver it.
489                    task.irq_signal_delivery_blocked
490                        .store(true, Ordering::Release);
491                    SIGNAL_IRQ_DEFERRED_COUNT.fetch_add(1, Ordering::Relaxed);
492                    return false;
493                }
494                debug_assert!(
495                    task.pending_signals.try_consume(signal),
496                    "signal vanished between peek and consume (stop path)"
497                );
498                return true;
499            }
500            DefaultAction::Term | DefaultAction::Core => {
501                if mode == SignalDeliveryMode::InterruptReturn {
502                    // kill_task requires a scheduler operation : not safe from IRQ context.
503                    // Leave the signal pending; the syscall-return path will deliver it.
504                    task.irq_signal_delivery_blocked
505                        .store(true, Ordering::Release);
506                    SIGNAL_IRQ_DEFERRED_COUNT.fetch_add(1, Ordering::Relaxed);
507                    return false;
508                }
509                if !task.pending_signals.try_consume(signal) {
510                    return false;
511                }
512                log::info!(
513                    "[signal] killing pid {} on SIG{}",
514                    task.pid,
515                    signal.as_u32()
516                );
517                crate::process::kill_task(task.id);
518                return true;
519            }
520        }
521    }
522
523    let handler = action.handler;
524    let restorer = action.restorer;
525    let sig_mask = action.mask;
526    let flags = action.flags;
527
528    if restorer == 0 {
529        if mode == SignalDeliveryMode::InterruptReturn {
530            // Cannot safely kill from IRQ context; leave signal pending.
531            task.irq_signal_delivery_blocked
532                .store(true, Ordering::Release);
533            SIGNAL_IRQ_DEFERRED_COUNT.fetch_add(1, Ordering::Relaxed);
534            return false;
535        }
536        if !task.pending_signals.try_consume(signal) {
537            return false;
538        }
539        log::warn!("[signal] no restorer for SIG{}, killing", signal.as_u32());
540        crate::process::kill_task(task.id);
541        return true;
542    }
543
544    if !task.pending_signals.try_consume(signal) {
545        return false;
546    }
547
548    let user_rsp = frame.iret_rsp;
549    let frame_size = core::mem::size_of::<SignalFrame>() as u64;
550    let new_rsp = ((user_rsp - frame_size) & !0xF) - 8;
551
552    let sig_frame = SignalFrame {
553        restorer,
554        signo: signal.as_u32() as u64,
555        saved_mask: task.blocked_signals.get_mask(),
556        rip: frame.iret_rip,
557        rsp: frame.iret_rsp,
558        rflags: frame.iret_rflags,
559        rax: frame.rax,
560        rcx: frame.rcx,
561        rdx: frame.rdx,
562        rbx: frame.rbx,
563        rbp: frame.rbp,
564        rsi: frame.rsi,
565        rdi: frame.rdi,
566        r8: frame.r8,
567        r9: frame.r9,
568        r10: frame.r10,
569        r11: frame.r11,
570        r12: frame.r12,
571        r13: frame.r13,
572        r14: frame.r14,
573        r15: frame.r15,
574        magic: SIGNAL_FRAME_MAGIC,
575    };
576
577    let bytes: &[u8] = unsafe {
578        core::slice::from_raw_parts(
579            &sig_frame as *const SignalFrame as *const u8,
580            core::mem::size_of::<SignalFrame>(),
581        )
582    };
583
584    match crate::memory::UserSliceWrite::new(new_rsp, bytes.len()) {
585        Ok(slice) => {
586            slice.copy_from(bytes);
587        }
588        Err(_) => {
589            if mode == SignalDeliveryMode::InterruptReturn {
590                task.irq_signal_delivery_blocked
591                    .store(true, Ordering::Release);
592                task.pending_signals.add(signal);
593                return false;
594            }
595            log::warn!("[signal] fault writing signal frame, killing");
596            crate::process::kill_task(task.id);
597            return true;
598        }
599    }
600
601    // Block signals specified in sa_mask, plus the signal itself (unless SA_NODEFER)
602    let mut new_mask = task.blocked_signals.get_mask() | sig_mask;
603    if flags & (SA_NODEFER as u64) == 0 {
604        new_mask |= signal.bit();
605    }
606    task.blocked_signals.set_mask(new_mask);
607
608    // SA_RESETHAND: reset handler to SIG_DFL after delivery
609    if flags & (SA_RESETHAND as u64) != 0 {
610        unsafe {
611            let actions = &mut *task.process.signal_actions.get();
612            actions[signal.as_u32() as usize] = SigActionData::default();
613        }
614    }
615
616    frame.iret_rip = handler;
617    frame.iret_rsp = new_rsp;
618    frame.rdi = signal.as_u32() as u64;
619    frame.rsi = 0;
620    frame.rdx = 0;
621
622    true
623}
624
625/// Performs the deliver pending signal operation.
626pub fn deliver_pending_signal(frame: &mut crate::syscall::SyscallFrame) -> bool {
627    deliver_pending_signal_inner(frame, SignalDeliveryMode::Normal)
628}
629
630/// Deliver a pending signal on the IRQ-return path to Ring 3.
631///
632/// Only one signal is delivered per call (the lowest-numbered unblocked one).
633/// Signals whose default action is Term, Core, or Stop, and signals without a
634/// restorer, are **deferred** : they remain pending and are delivered on the
635/// next syscall-return path, which is safe to kill or suspend the task.
636/// The deferral count is tracked in [`SIGNAL_IRQ_DEFERRED_COUNT`].
637///
638/// This mirrors Linux's `do_notify_resume()` called from `ret_from_intr`.
639pub fn deliver_pending_signal_on_interrupt_return(
640    frame: &mut crate::syscall::SyscallFrame,
641) -> bool {
642    deliver_pending_signal_inner(frame, SignalDeliveryMode::InterruptReturn)
643}
644
645/// Signal alternate stack
646#[derive(Debug, Clone, Copy, Default)]
647#[repr(C)]
648pub struct SigStack {
649    /// Stack base address
650    pub ss_sp: u64,
651    /// Stack flags
652    pub ss_flags: i32,
653    /// Stack size
654    pub ss_size: usize,
655}
656
657/// Send a signal to a task.
658///
659/// # Arguments
660///
661/// - `target`: Task ID to send the signal to
662/// - `signal`: Signal to send
663///
664/// # Returns
665///
666/// - `Ok(())` if the signal was delivered
667/// - `Err(InvalidArgument)` if the task doesn't exist or signal is invalid
668pub fn send_signal(
669    target: crate::process::TaskId,
670    signal: Signal,
671) -> Result<(), crate::syscall::error::SyscallError> {
672    use crate::{process::get_task_by_id, syscall::error::SyscallError};
673
674    // SIGKILL and SIGSTOP cannot be ignored
675    if signal.is_uncatchable() {
676        // Still deliver them
677    }
678
679    let task = get_task_by_id(target).ok_or(SyscallError::InvalidArgument)?;
680
681    // Add signal to the task's pending set.
682    // We have a reference to the task, so it's safe to access its fields.
683    let pending = &task.pending_signals;
684    pending.add(signal);
685
686    // If the task is blocked and the signal is not blocked, wake it.
687    // Best-effort read: state may change concurrently, but wake_task is
688    // idempotent : a spurious wake is harmless and a missed wake will be
689    // caught on the next scheduling point when pending_signals is checked.
690    {
691        let state = task.get_state();
692        if state == crate::process::TaskState::Blocked {
693            let blocked = &task.blocked_signals;
694            if !blocked.contains(signal) {
695                // Wake the task so it can handle the signal.
696                crate::process::wake_task(target);
697            }
698        }
699    }
700
701    Ok(())
702}
703
704/// Check if the current task has any pending signals.
705///
706/// Used by blocking syscalls to determine if they should return EINTR.
707pub fn has_pending_signals() -> bool {
708    use crate::process::current_task_clone;
709
710    if let Some(task) = current_task_clone() {
711        let pending = &task.pending_signals;
712        let blocked = &task.blocked_signals;
713        pending.unblocked(blocked) != 0
714    } else {
715        false
716    }
717}
718
719/// Consume the next pending signal.
720///
721/// Atomically removes the signal from the pending set and returns it.
722pub fn consume_next_signal() -> Option<Signal> {
723    use crate::process::current_task_clone;
724
725    if let Some(task) = current_task_clone() {
726        task.pending_signals
727            .consume_one_unblocked(&task.blocked_signals)
728    } else {
729        None
730    }
731}