⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/fs/
signalfd.rs

1//! signalfd -- Signal notification file descriptor
2//!
3//! Provides a file descriptor for receiving signals via read(2) instead
4//! of signal handlers. Integrable with epoll/poll for unified I/O event
5//! loops. Used by D-Bus daemon and systemd-compatible service managers.
6//!
7//! ## Syscall Interface
8//! - `signalfd_create(fd, mask_ptr, mask_size, flags) -> fd` (syscall 334)
9//! - Read via standard `read(2)` on returned fd
10//!
11//! ## Semantics
12//! - **read**: Returns one or more `SignalfdSiginfo` structs (128 bytes each)
13//!   for pending signals in the mask. Signals consumed by signalfd are removed
14//!   from the process's pending set.
15//! - Creating with fd == -1 creates a new signalfd; passing an existing fd
16//!   updates the signal mask.
17
18#![allow(dead_code)]
19
20use alloc::{collections::BTreeMap, vec::Vec};
21use core::sync::atomic::{AtomicU64, Ordering};
22
23use spin::Mutex;
24
25use crate::syscall::{SyscallError, SyscallResult};
26
27/// Maximum number of signalfd instances system-wide.
28const MAX_SIGNALFD_INSTANCES: usize = 1024;
29
30/// SFD_NONBLOCK: Return EAGAIN instead of blocking on empty read.
31pub const SFD_NONBLOCK: u32 = 0x800;
32/// SFD_CLOEXEC: Set close-on-exec.
33pub const SFD_CLOEXEC: u32 = 0x80000;
34
35/// Maximum signal number we track.
36const MAX_SIGNAL: usize = 64;
37
38/// Signal information returned by read(2) on a signalfd.
39/// Matches Linux's `struct signalfd_siginfo` layout (128 bytes).
40#[repr(C)]
41#[derive(Debug, Clone, Copy)]
42pub struct SignalfdSiginfo {
43    /// Signal number.
44    pub ssi_signo: u32,
45    /// Error number (unused, 0).
46    pub ssi_errno: i32,
47    /// Signal code.
48    pub ssi_code: i32,
49    /// Sending PID.
50    pub ssi_pid: u32,
51    /// Sending UID.
52    pub ssi_uid: u32,
53    /// File descriptor (for SIGIO).
54    pub ssi_fd: i32,
55    /// Kernel timer ID.
56    pub ssi_tid: u32,
57    /// Band event (for SIGIO).
58    pub ssi_band: u32,
59    /// POSIX timer overrun count.
60    pub ssi_overrun: u32,
61    /// Trap number.
62    pub ssi_trapno: u32,
63    /// Exit status or signal (for SIGCHLD).
64    pub ssi_status: i32,
65    /// Integer sent by sigqueue.
66    pub ssi_int: i32,
67    /// Pointer sent by sigqueue.
68    pub ssi_ptr: u64,
69    /// User CPU time consumed (for SIGCHLD).
70    pub ssi_utime: u64,
71    /// System CPU time consumed (for SIGCHLD).
72    pub ssi_stime: u64,
73    /// Address that generated signal (for hardware signals).
74    pub ssi_addr: u64,
75    /// Address LSB (for SIGBUS).
76    pub ssi_addr_lsb: u16,
77    /// Padding to 128 bytes.
78    _pad: [u8; 46],
79}
80
81impl Default for SignalfdSiginfo {
82    fn default() -> Self {
83        // SAFETY: SignalfdSiginfo is repr(C) and all-zeros is valid.
84        unsafe { core::mem::zeroed() }
85    }
86}
87
88/// Signal mask -- a bitmask of signal numbers to monitor.
89#[derive(Debug, Clone, Default)]
90pub struct SigSet {
91    bits: u64,
92}
93
94impl SigSet {
95    pub fn new() -> Self {
96        Self { bits: 0 }
97    }
98
99    pub fn is_set(&self, signum: u32) -> bool {
100        if signum == 0 || signum as usize > MAX_SIGNAL {
101            return false;
102        }
103        (self.bits & (1u64 << (signum - 1))) != 0
104    }
105
106    pub fn set(&mut self, signum: u32) {
107        if signum > 0 && (signum as usize) <= MAX_SIGNAL {
108            self.bits |= 1u64 << (signum - 1);
109        }
110    }
111
112    pub fn from_raw(bits: u64) -> Self {
113        Self { bits }
114    }
115
116    pub fn raw(&self) -> u64 {
117        self.bits
118    }
119}
120
121/// Pending signal queued for delivery via signalfd.
122#[derive(Debug, Clone)]
123struct PendingSignal {
124    signum: u32,
125    sender_pid: u32,
126}
127
128/// Internal signalfd instance.
129struct SignalFdInstance {
130    /// Signal mask -- which signals to accept.
131    mask: SigSet,
132    /// Whether non-blocking mode is active.
133    nonblock: bool,
134    /// Queue of pending signals.
135    pending: Vec<PendingSignal>,
136    /// Owner process ID.
137    owner_pid: u64,
138}
139
140/// Global registry of signalfd instances.
141static SIGNALFD_REGISTRY: Mutex<BTreeMap<u32, SignalFdInstance>> = Mutex::new(BTreeMap::new());
142
143/// Next ID for signalfd allocation.
144static NEXT_SIGNALFD_ID: AtomicU64 = AtomicU64::new(1);
145
146/// Create or update a signalfd.
147///
148/// # Arguments
149/// - `fd`: -1 to create new, or existing signalfd ID to update mask.
150/// - `mask`: Signal mask (bitmask of signals to monitor).
151/// - `flags`: Combination of `SFD_NONBLOCK`, `SFD_CLOEXEC`.
152///
153/// # Returns
154/// The signalfd ID on success.
155pub fn signalfd_create(fd: i32, mask: u64, flags: u32) -> SyscallResult {
156    let pid = crate::process::current_process()
157        .map(|p| p.pid.0)
158        .unwrap_or(0);
159
160    if fd != -1 {
161        // Update existing signalfd mask
162        let sfd_id = fd as u32;
163        let mut registry = SIGNALFD_REGISTRY.lock();
164        let instance = registry
165            .get_mut(&sfd_id)
166            .ok_or(SyscallError::BadFileDescriptor)?;
167        instance.mask = SigSet::from_raw(mask);
168        return Ok(sfd_id as usize);
169    }
170
171    // Create new
172    let nonblock = (flags & SFD_NONBLOCK) != 0;
173
174    let instance = SignalFdInstance {
175        mask: SigSet::from_raw(mask),
176        nonblock,
177        pending: Vec::new(),
178        owner_pid: pid,
179    };
180
181    let id = NEXT_SIGNALFD_ID.fetch_add(1, Ordering::Relaxed) as u32;
182
183    let mut registry = SIGNALFD_REGISTRY.lock();
184    if registry.len() >= MAX_SIGNALFD_INSTANCES {
185        return Err(SyscallError::OutOfMemory);
186    }
187    registry.insert(id, instance);
188    Ok(id as usize)
189}
190
191/// Deliver a signal to all signalfds of a given process that have
192/// the signal in their mask.
193///
194/// Called from the kernel's signal delivery path.
195pub fn deliver_signal(target_pid: u64, signum: u32, sender_pid: u32) {
196    let mut registry = SIGNALFD_REGISTRY.lock();
197    for instance in registry.values_mut() {
198        if instance.owner_pid == target_pid && instance.mask.is_set(signum) {
199            instance.pending.push(PendingSignal { signum, sender_pid });
200        }
201    }
202}
203
204/// Read one pending signal from a signalfd.
205///
206/// Returns a `SignalfdSiginfo` struct for the oldest pending signal
207/// in the mask, or EAGAIN/WouldBlock if no signals pending.
208pub fn signalfd_read(sfd_id: u32) -> Result<SignalfdSiginfo, SyscallError> {
209    let start = crate::timer::get_uptime_ms();
210    const MAX_BLOCK_MS: u64 = 30_000;
211
212    loop {
213        let mut registry = SIGNALFD_REGISTRY.lock();
214        let instance = registry
215            .get_mut(&sfd_id)
216            .ok_or(SyscallError::BadFileDescriptor)?;
217
218        if !instance.pending.is_empty() {
219            let sig = instance.pending.remove(0);
220            return Ok(SignalfdSiginfo {
221                ssi_signo: sig.signum,
222                ssi_pid: sig.sender_pid,
223                ..SignalfdSiginfo::default()
224            });
225        }
226
227        if instance.nonblock {
228            return Err(SyscallError::WouldBlock);
229        }
230
231        drop(registry);
232        if crate::timer::get_uptime_ms() - start >= MAX_BLOCK_MS {
233            return Err(SyscallError::WouldBlock);
234        }
235        crate::sched::yield_cpu();
236    }
237}
238
239/// Query whether a signalfd has pending signals.
240/// Used by epoll to check readiness without consuming data.
241pub fn is_readable(sfd_id: u32) -> bool {
242    let registry = SIGNALFD_REGISTRY.lock();
243    registry.get(&sfd_id).is_some_and(|i| !i.pending.is_empty())
244}
245
246/// Close (destroy) a signalfd instance.
247pub fn signalfd_close(sfd_id: u32) -> SyscallResult {
248    let mut registry = SIGNALFD_REGISTRY.lock();
249    registry
250        .remove(&sfd_id)
251        .ok_or(SyscallError::BadFileDescriptor)?;
252    Ok(0)
253}
254
255// ── VfsNode adapter ────────────────────────────────────────────────────
256
257use alloc::sync::Arc;
258
259use super::{DirEntry, Metadata, NodeType, Permissions, VfsNode};
260use crate::error::KernelError;
261
262/// VfsNode wrapper around a signalfd instance.
263///
264/// This allows signalfd to be inserted into a process's file table so that
265/// standard read()/close()/epoll work on it. musl's signalfd4() syscall
266/// expects a real file descriptor.
267pub struct SignalFdNode {
268    sfd_id: u32,
269}
270
271impl SignalFdNode {
272    pub fn new(sfd_id: u32) -> Self {
273        Self { sfd_id }
274    }
275
276    /// Get the internal signalfd ID (needed for mask updates).
277    pub fn sfd_id(&self) -> u32 {
278        self.sfd_id
279    }
280}
281
282impl VfsNode for SignalFdNode {
283    fn node_type(&self) -> NodeType {
284        NodeType::CharDevice
285    }
286
287    fn read(&self, _offset: usize, buffer: &mut [u8]) -> Result<usize, KernelError> {
288        if buffer.len() < 128 {
289            return Err(KernelError::InvalidArgument {
290                name: "buflen",
291                value: "must be at least 128 bytes for signalfd",
292            });
293        }
294        let info = signalfd_read(self.sfd_id).map_err(|e| match e {
295            SyscallError::WouldBlock => KernelError::WouldBlock,
296            _ => KernelError::FsError(crate::error::FsError::BadFileDescriptor),
297        })?;
298        // SAFETY: SignalfdSiginfo is repr(C) and exactly 128 bytes.
299        let bytes = unsafe {
300            core::slice::from_raw_parts(&info as *const SignalfdSiginfo as *const u8, 128)
301        };
302        buffer[..128].copy_from_slice(bytes);
303        Ok(128)
304    }
305
306    fn write(&self, _offset: usize, _data: &[u8]) -> Result<usize, KernelError> {
307        // signalfd is not writable via write(2)
308        Err(KernelError::PermissionDenied {
309            operation: "write signalfd",
310        })
311    }
312
313    fn poll_readiness(&self) -> u16 {
314        let mut events = 0u16;
315        if is_readable(self.sfd_id) {
316            events |= 0x0001; // POLLIN
317        }
318        events
319    }
320
321    fn metadata(&self) -> Result<Metadata, KernelError> {
322        Ok(Metadata {
323            size: 0,
324            node_type: NodeType::CharDevice,
325            permissions: Permissions::from_mode(0o666),
326            uid: 0,
327            gid: 0,
328            created: 0,
329            modified: 0,
330            accessed: 0,
331            inode: 0,
332        })
333    }
334
335    fn readdir(&self) -> Result<Vec<DirEntry>, KernelError> {
336        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
337    }
338
339    fn lookup(&self, _name: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
340        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
341    }
342
343    fn create(
344        &self,
345        _name: &str,
346        _permissions: Permissions,
347    ) -> Result<Arc<dyn VfsNode>, KernelError> {
348        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
349    }
350
351    fn mkdir(
352        &self,
353        _name: &str,
354        _permissions: Permissions,
355    ) -> Result<Arc<dyn VfsNode>, KernelError> {
356        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
357    }
358
359    fn unlink(&self, _name: &str) -> Result<(), KernelError> {
360        Err(KernelError::FsError(crate::error::FsError::NotADirectory))
361    }
362
363    fn truncate(&self, _size: usize) -> Result<(), KernelError> {
364        Err(KernelError::PermissionDenied {
365            operation: "truncate signalfd",
366        })
367    }
368}
369
370impl Drop for SignalFdNode {
371    fn drop(&mut self) {
372        let _ = signalfd_close(self.sfd_id);
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379
380    #[test]
381    fn test_sigset_basic() {
382        let mut s = SigSet::new();
383        assert!(!s.is_set(1));
384        s.set(1); // SIGHUP
385        assert!(s.is_set(1));
386        assert!(!s.is_set(2));
387    }
388
389    #[test]
390    fn test_sigset_from_raw() {
391        let s = SigSet::from_raw(0b110); // signals 2 and 3
392        assert!(!s.is_set(1));
393        assert!(s.is_set(2));
394        assert!(s.is_set(3));
395        assert!(!s.is_set(4));
396    }
397
398    #[test]
399    fn test_sigset_boundary() {
400        let mut s = SigSet::new();
401        s.set(0); // invalid -- should be no-op
402        assert!(!s.is_set(0));
403        s.set(64);
404        assert!(s.is_set(64));
405        s.set(65); // out of range -- no-op
406        assert!(!s.is_set(65));
407    }
408
409    #[test]
410    fn test_signalfd_create_and_close() {
411        SIGNALFD_REGISTRY.lock().clear();
412
413        let id = signalfd_create(-1, 0b10, SFD_NONBLOCK).unwrap() as u32;
414        signalfd_close(id).unwrap();
415        assert!(signalfd_read(id).is_err());
416    }
417
418    #[test]
419    fn test_signalfd_update_mask() {
420        SIGNALFD_REGISTRY.lock().clear();
421
422        let id = signalfd_create(-1, 0b10, 0).unwrap();
423        // Update mask to include signal 3
424        signalfd_create(id as i32, 0b110, 0).unwrap();
425
426        // Verify via deliver + read
427        deliver_signal(0, 3, 42);
428        let info = signalfd_read(id as u32).unwrap();
429        assert_eq!(info.ssi_signo, 3);
430        assert_eq!(info.ssi_pid, 42);
431    }
432
433    #[test]
434    fn test_signalfd_deliver_and_read() {
435        SIGNALFD_REGISTRY.lock().clear();
436
437        // Mask signal 2 (SIGINT)
438        let id = signalfd_create(-1, 0b10, 0).unwrap() as u32;
439
440        // No signals yet
441        assert!(signalfd_read(id).is_err());
442
443        // Deliver signal 2 to pid 0
444        deliver_signal(0, 2, 100);
445
446        let info = signalfd_read(id).unwrap();
447        assert_eq!(info.ssi_signo, 2);
448        assert_eq!(info.ssi_pid, 100);
449
450        // Queue is now empty
451        assert!(signalfd_read(id).is_err());
452    }
453
454    #[test]
455    fn test_signalfd_ignores_unmasked() {
456        SIGNALFD_REGISTRY.lock().clear();
457
458        // Only mask signal 1
459        let id = signalfd_create(-1, 0b1, SFD_NONBLOCK).unwrap() as u32;
460
461        // Deliver signal 2 (not in mask)
462        deliver_signal(0, 2, 50);
463
464        // Should have nothing
465        assert!(signalfd_read(id).is_err());
466    }
467
468    #[test]
469    fn test_signalfd_siginfo_size() {
470        assert_eq!(core::mem::size_of::<SignalfdSiginfo>(), 128);
471    }
472}