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
251/// Signal action flags
252pub const SA_NOCLDSTOP: u32 = 1 << 0;
253pub const SA_NOCLDWAIT: u32 = 1 << 1;
254pub const SA_SIGINFO: u32 = 1 << 2;
255pub const SA_RESTORER: u32 = 1 << 3;
256pub const SA_ONSTACK: u32 = 1 << 4;
257pub const SA_RESTART: u32 = 1 << 5;
258pub const SA_NODEFER: u32 = 1 << 6;
259pub const SA_RESETHAND: u32 = 1 << 7;
260
261pub const SIG_DFL: u64 = 0;
262pub const SIG_IGN: u64 = 1;
263
264#[derive(Debug, Clone, Copy)]
265#[repr(C)]
266pub struct SigActionData {
267    pub handler: u64,
268    pub flags: u64,
269    pub restorer: u64,
270    pub mask: u64,
271}
272
273impl SigActionData {
274    /// Builds a default instance.
275    pub const fn default() -> Self {
276        Self {
277            handler: SIG_DFL,
278            flags: 0,
279            restorer: 0,
280            mask: 0,
281        }
282    }
283
284    /// Returns whether default.
285    pub fn is_default(&self) -> bool {
286        self.handler == SIG_DFL
287    }
288    /// Returns whether ignore.
289    pub fn is_ignore(&self) -> bool {
290        self.handler == SIG_IGN
291    }
292    /// Returns whether user handler.
293    pub fn is_user_handler(&self) -> bool {
294        self.handler > 1
295    }
296}
297
298impl Default for SigActionData {
299    /// Builds a default instance.
300    fn default() -> Self {
301        Self::default()
302    }
303}
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub enum DefaultAction {
307    Term,
308    Core,
309    Stop,
310    Cont,
311    Ign,
312}
313
314impl Signal {
315    /// Performs the default action operation.
316    pub fn default_action(self) -> DefaultAction {
317        match self {
318            Signal::SIGHUP
319            | Signal::SIGINT
320            | Signal::SIGPIPE
321            | Signal::SIGALRM
322            | Signal::SIGTERM
323            | Signal::SIGUSR1
324            | Signal::SIGUSR2
325            | Signal::SIGPROF
326            | Signal::SIGVTALRM
327            | Signal::SIGIO
328            | Signal::SIGPWR
329            | Signal::SIGSYS => DefaultAction::Term,
330
331            Signal::SIGQUIT
332            | Signal::SIGILL
333            | Signal::SIGABRT
334            | Signal::SIGFPE
335            | Signal::SIGSEGV
336            | Signal::SIGBUS
337            | Signal::SIGTRAP
338            | Signal::SIGXCPU
339            | Signal::SIGXFSZ => DefaultAction::Core,
340
341            Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
342                DefaultAction::Stop
343            }
344
345            Signal::SIGCONT => DefaultAction::Cont,
346
347            Signal::SIGCHLD | Signal::SIGURG | Signal::SIGWINCH => DefaultAction::Ign,
348
349            Signal::SIGKILL => DefaultAction::Term,
350        }
351    }
352}
353
354pub const SIGNAL_FRAME_MAGIC: u64 = 0x5354_5239_5349_4700;
355
356#[derive(Debug, Clone, Copy)]
357#[repr(C)]
358pub struct SignalFrame {
359    pub restorer: u64,
360    pub signo: u64,
361    pub saved_mask: u64,
362    pub rip: u64,
363    pub rsp: u64,
364    pub rflags: u64,
365    pub rax: u64,
366    pub rcx: u64,
367    pub rdx: u64,
368    pub rbx: u64,
369    pub rbp: u64,
370    pub rsi: u64,
371    pub rdi: u64,
372    pub r8: u64,
373    pub r9: u64,
374    pub r10: u64,
375    pub r11: u64,
376    pub r12: u64,
377    pub r13: u64,
378    pub r14: u64,
379    pub r15: u64,
380    pub magic: u64,
381}
382
383/// Performs the deliver pending signal operation.
384pub fn deliver_pending_signal(frame: &mut crate::syscall::SyscallFrame) -> bool {
385    let task = match crate::process::current_task_clone() {
386        Some(t) => t,
387        None => return false,
388    };
389
390    if task.is_kernel() {
391        return false;
392    }
393
394    let signal = match task
395        .pending_signals
396        .consume_one_unblocked(&task.blocked_signals)
397    {
398        Some(s) => s,
399        None => return false,
400    };
401
402    let action = unsafe {
403        let actions = &*task.process.signal_actions.get();
404        actions[signal.as_u32() as usize]
405    };
406
407    if action.is_ignore() {
408        return true;
409    }
410
411    if action.is_default() {
412        match signal.default_action() {
413            DefaultAction::Ign => return true,
414            DefaultAction::Cont => return true,
415            DefaultAction::Stop => {
416                return true;
417            }
418            DefaultAction::Term | DefaultAction::Core => {
419                log::info!(
420                    "[signal] killing pid {} on SIG{}",
421                    task.pid,
422                    signal.as_u32()
423                );
424                crate::process::kill_task(task.id);
425                return true;
426            }
427        }
428    }
429
430    let handler = action.handler;
431    let restorer = action.restorer;
432    let sig_mask = action.mask;
433    let flags = action.flags;
434
435    if restorer == 0 {
436        log::warn!("[signal] no restorer for SIG{}, killing", signal.as_u32());
437        crate::process::kill_task(task.id);
438        return true;
439    }
440
441    let user_rsp = frame.iret_rsp;
442    let frame_size = core::mem::size_of::<SignalFrame>() as u64;
443    let new_rsp = ((user_rsp - frame_size) & !0xF) - 8;
444
445    let sig_frame = SignalFrame {
446        restorer,
447        signo: signal.as_u32() as u64,
448        saved_mask: task.blocked_signals.get_mask(),
449        rip: frame.iret_rip,
450        rsp: frame.iret_rsp,
451        rflags: frame.iret_rflags,
452        rax: frame.rax,
453        rcx: frame.rcx,
454        rdx: frame.rdx,
455        rbx: frame.rbx,
456        rbp: frame.rbp,
457        rsi: frame.rsi,
458        rdi: frame.rdi,
459        r8: frame.r8,
460        r9: frame.r9,
461        r10: frame.r10,
462        r11: frame.r11,
463        r12: frame.r12,
464        r13: frame.r13,
465        r14: frame.r14,
466        r15: frame.r15,
467        magic: SIGNAL_FRAME_MAGIC,
468    };
469
470    let bytes: &[u8] = unsafe {
471        core::slice::from_raw_parts(
472            &sig_frame as *const SignalFrame as *const u8,
473            core::mem::size_of::<SignalFrame>(),
474        )
475    };
476
477    match crate::memory::UserSliceWrite::new(new_rsp, bytes.len()) {
478        Ok(slice) => {
479            slice.copy_from(bytes);
480        }
481        Err(_) => {
482            log::warn!("[signal] fault writing signal frame, killing");
483            crate::process::kill_task(task.id);
484            return true;
485        }
486    }
487
488    // Block signals specified in sa_mask, plus the signal itself (unless SA_NODEFER)
489    let mut new_mask = task.blocked_signals.get_mask() | sig_mask;
490    if flags & (SA_NODEFER as u64) == 0 {
491        new_mask |= signal.bit();
492    }
493    task.blocked_signals.set_mask(new_mask);
494
495    // SA_RESETHAND: reset handler to SIG_DFL after delivery
496    if flags & (SA_RESETHAND as u64) != 0 {
497        unsafe {
498            let actions = &mut *task.process.signal_actions.get();
499            actions[signal.as_u32() as usize] = SigActionData::default();
500        }
501    }
502
503    frame.iret_rip = handler;
504    frame.iret_rsp = new_rsp;
505    frame.rdi = signal.as_u32() as u64;
506    frame.rsi = 0;
507    frame.rdx = 0;
508
509    true
510}
511
512/// Signal alternate stack
513#[derive(Debug, Clone, Copy, Default)]
514#[repr(C)]
515pub struct SigStack {
516    /// Stack base address
517    pub ss_sp: u64,
518    /// Stack flags
519    pub ss_flags: i32,
520    /// Stack size
521    pub ss_size: usize,
522}
523
524/// Send a signal to a task.
525///
526/// # Arguments
527///
528/// - `target`: Task ID to send the signal to
529/// - `signal`: Signal to send
530///
531/// # Returns
532///
533/// - `Ok(())` if the signal was delivered
534/// - `Err(InvalidArgument)` if the task doesn't exist or signal is invalid
535pub fn send_signal(
536    target: crate::process::TaskId,
537    signal: Signal,
538) -> Result<(), crate::syscall::error::SyscallError> {
539    use crate::{process::get_task_by_id, syscall::error::SyscallError};
540
541    // SIGKILL and SIGSTOP cannot be ignored
542    if signal.is_uncatchable() {
543        // Still deliver them
544    }
545
546    let task = get_task_by_id(target).ok_or(SyscallError::InvalidArgument)?;
547
548    // Add signal to the task's pending set.
549    // We have a reference to the task, so it's safe to access its fields.
550    let pending = &task.pending_signals;
551    pending.add(signal);
552
553    // If the task is blocked and the signal is not blocked, wake it.
554    // SAFETY: Best-effort read of task.state without the scheduler lock.
555    // The state may change concurrently, but wake_task is idempotent —
556    // a spurious wake is harmless and a missed wake will be caught on
557    // the next scheduling point when pending_signals is checked.
558    unsafe {
559        let state = &*task.state.get();
560        if *state == crate::process::TaskState::Blocked {
561            let blocked = &task.blocked_signals;
562            if !blocked.contains(signal) {
563                // Wake the task so it can handle the signal.
564                crate::process::wake_task(target);
565            }
566        }
567    }
568
569    Ok(())
570}
571
572/// Check if the current task has any pending signals.
573///
574/// Used by blocking syscalls to determine if they should return EINTR.
575pub fn has_pending_signals() -> bool {
576    use crate::process::current_task_clone;
577
578    if let Some(task) = current_task_clone() {
579        let pending = &task.pending_signals;
580        let blocked = &task.blocked_signals;
581        pending.unblocked(blocked) != 0
582    } else {
583        false
584    }
585}
586
587/// Consume the next pending signal.
588///
589/// Atomically removes the signal from the pending set and returns it.
590pub fn consume_next_signal() -> Option<Signal> {
591    use crate::process::current_task_clone;
592
593    if let Some(task) = current_task_clone() {
594        task.pending_signals
595            .consume_one_unblocked(&task.blocked_signals)
596    } else {
597        None
598    }
599}