// SPDX-License-Identifier: MPL-2.0 //! signalfd implementation for Linux compatibility //! //! The signalfd mechanism allows receiving signals via file descriptor, //! enabling better integration with event loops. //! See https://man7.org/linux/man-pages/man2/signalfd.2.html use core::sync::atomic::{AtomicBool, Ordering}; use bitflags::bitflags; use super::SyscallReturn; use crate::{ events::{IoEvents, Observer}, fs::{ file_handle::FileLike, file_table::{get_file_fast, FdFlags, FileDesc}, utils::{CreationFlags, InodeMode, InodeType, Metadata, StatusFlags}, }, prelude::*, process::{ posix_thread::AsPosixThread, signal::{ constants::{SIGKILL, SIGSTOP}, sig_mask::{AtomicSigMask, SigMask}, signals::Signal, PollHandle, Pollable, Pollee, SigEvents, SigEventsFilter, }, Gid, Uid, }, time::clocks::RealTimeClock, }; /// Creates a new signalfd or updates an existing one according to the given mask pub fn sys_signalfd( fd: FileDesc, mask_ptr: Vaddr, sizemask: usize, ctx: &Context, ) -> Result { sys_signalfd4(fd, mask_ptr, sizemask, 0, ctx) } /// Creates a new signalfd or updates an existing one according to the given mask and flags pub fn sys_signalfd4( fd: FileDesc, mask_ptr: Vaddr, sizemask: usize, flags: i32, ctx: &Context, ) -> Result { debug!( "fd = {}, mask = {:x}, sizemask = {}, flags = {}", fd, mask_ptr, sizemask, flags ); if sizemask != core::mem::size_of::() { return Err(Error::with_message(Errno::EINVAL, "invalid mask size")); } let mut mask = ctx.user_space().read_val::(mask_ptr)?; mask -= SIGKILL; mask -= SIGSTOP; let flags = SignalFileFlags::from_bits(flags as u32) .ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid flags"))?; let fd_flags = if flags.contains(SignalFileFlags::O_CLOEXEC) { FdFlags::CLOEXEC } else { FdFlags::empty() }; let non_blocking = flags.contains(SignalFileFlags::O_NONBLOCK); let new_fd = if fd == -1 { create_new_signalfd(ctx, mask, non_blocking, fd_flags)? } else { update_existing_signalfd(ctx, fd, mask, non_blocking)? }; Ok(SyscallReturn::Return(new_fd as _)) } fn create_new_signalfd( ctx: &Context, mask: SigMask, non_blocking: bool, fd_flags: FdFlags, ) -> Result { let atomic_mask = AtomicSigMask::new(mask); let signal_file = SignalFile::new(atomic_mask, non_blocking); register_observer(ctx, &signal_file, mask)?; let file_table = ctx.thread_local.borrow_file_table(); let fd = file_table.unwrap().write().insert(signal_file, fd_flags); Ok(fd) } fn update_existing_signalfd( ctx: &Context, fd: FileDesc, new_mask: SigMask, non_blocking: bool, ) -> Result { let mut file_table = ctx.thread_local.borrow_file_table_mut(); let file = get_file_fast!(&mut file_table, fd); let signal_file = file .downcast_ref::() .ok_or_else(|| Error::with_message(Errno::EINVAL, "File descriptor is not a signalfd"))?; if signal_file.mask().load(Ordering::Relaxed) != new_mask { signal_file.update_signal_mask(new_mask)?; } signal_file.set_non_blocking(non_blocking); Ok(fd) } fn register_observer(ctx: &Context, signal_file: &Arc, mask: SigMask) -> Result<()> { let filter = SigEventsFilter::new(mask); ctx.posix_thread .register_sigqueue_observer(signal_file.observer_ref(), filter); Ok(()) } bitflags! { /// Signal file descriptor creation flags struct SignalFileFlags: u32 { const O_CLOEXEC = CreationFlags::O_CLOEXEC.bits(); const O_NONBLOCK = StatusFlags::O_NONBLOCK.bits(); } } /// Signal file implementation /// /// Represents a file that can be used to receive signals /// as readable events. struct SignalFile { /// Atomic signal mask for filtering signals signals_mask: AtomicSigMask, /// I/O event notifier pollee: Pollee, /// Non-blocking mode flag non_blocking: AtomicBool, /// Weak reference to self as an observer weak_self: Weak>, } impl SignalFile { /// Create a new signalfd instance fn new(mask: AtomicSigMask, non_blocking: bool) -> Arc { Arc::new_cyclic(|weak_ref| { let weak_self = weak_ref.clone() as Weak>; Self { signals_mask: mask, pollee: Pollee::new(), non_blocking: AtomicBool::new(non_blocking), weak_self, } }) } fn mask(&self) -> &AtomicSigMask { &self.signals_mask } fn observer_ref(&self) -> Weak> { self.weak_self.clone() } fn update_signal_mask(&self, new_mask: SigMask) -> Result<()> { if let Some(thread) = current_thread!().as_posix_thread() { thread.unregister_sigqueue_observer(&self.weak_self); let filter = SigEventsFilter::new(new_mask); thread.register_sigqueue_observer(self.weak_self.clone(), filter); } self.signals_mask.store(new_mask, Ordering::Relaxed); Ok(()) } fn set_non_blocking(&self, non_blocking: bool) { self.non_blocking.store(non_blocking, Ordering::Relaxed); } fn is_non_blocking(&self) -> bool { self.non_blocking.load(Ordering::Relaxed) } /// Check current readable I/O events fn check_io_events(&self) -> IoEvents { let current = current_thread!(); let Some(thread) = current.as_posix_thread() else { return IoEvents::empty(); }; let mask = self.signals_mask.load(Ordering::Relaxed); if thread.sig_pending().intersects(mask) { IoEvents::IN } else { IoEvents::empty() } } /// Attempt non-blocking read operation fn try_read(&self, writer: &mut VmWriter) -> Result { let current = current_thread!(); let thread = current .as_posix_thread() .ok_or_else(|| Error::with_message(Errno::ESRCH, "Not a POSIX thread"))?; // Mask is inverted to get the signals that are not blocked let mask = !self.signals_mask.load(Ordering::Relaxed); let max_signals = writer.avail() / core::mem::size_of::(); let mut count = 0; for _ in 0..max_signals { match thread.dequeue_signal(&mask) { Some(signal) => { writer.write_val(&signal.to_signalfd_siginfo())?; count += 1; self.pollee.invalidate(); } None => break, } } if count == 0 { return_errno!(Errno::EAGAIN); } Ok(count * core::mem::size_of::()) } } impl Observer for SignalFile { // TODO: Fix signal notifications. // Child processes do not inherit the parent's observer mechanism for signal event notifications. // `sys_poll` with blocking mode gets stuck if the signal is received after polling. fn on_events(&self, events: &SigEvents) { if self .signals_mask .load(Ordering::Relaxed) .contains(events.sig_num()) { self.pollee.notify(IoEvents::IN); } } } impl Pollable for SignalFile { fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { self.pollee .poll_with(mask, poller, || self.check_io_events()) } } impl FileLike for SignalFile { fn read(&self, writer: &mut VmWriter) -> Result { if writer.avail() < core::mem::size_of::() { return_errno_with_message!(Errno::EINVAL, "Buffer too small for siginfo structure"); } if self.is_non_blocking() { self.try_read(writer) } else { self.wait_events(IoEvents::IN, None, || self.try_read(writer)) } } fn write(&self, _reader: &mut VmReader) -> Result { return_errno_with_message!(Errno::EBADF, "signalfd does not support write operations"); } fn status_flags(&self) -> StatusFlags { if self.is_non_blocking() { StatusFlags::O_NONBLOCK } else { StatusFlags::empty() } } fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> { self.set_non_blocking(new_flags.contains(StatusFlags::O_NONBLOCK)); Ok(()) } fn metadata(&self) -> Metadata { let now = RealTimeClock::get().read_time(); Metadata { dev: 0, ino: 0, size: 0, blk_size: 0, blocks: 0, atime: now, mtime: now, ctime: now, type_: InodeType::NamedPipe, mode: InodeMode::from_bits_truncate(0o400), nlinks: 1, uid: Uid::new_root(), gid: Gid::new_root(), rdev: 0, } } } impl Drop for SignalFile { // TODO: Fix signal notifications. See `on_events` method. fn drop(&mut self) { if let Some(thread) = current_thread!().as_posix_thread() { thread.unregister_sigqueue_observer(&self.weak_self); } } } #[repr(C)] #[derive(Debug, Copy, Clone, Pod)] struct SignalfdSiginfo { ssi_signo: u32, ssi_errno: i32, ssi_code: i32, ssi_pid: u32, ssi_uid: u32, ssi_fd: i32, ssi_tid: u32, ssi_band: u32, ssi_overrun: u32, ssi_trapno: u32, ssi_status: i32, ssi_int: i32, ssi_ptr: u64, ssi_utime: u64, ssi_stime: u64, ssi_addr: u64, _pad: [u8; 48], } trait ToSignalfdSiginfo { fn to_signalfd_siginfo(&self) -> SignalfdSiginfo; } impl ToSignalfdSiginfo for Box { fn to_signalfd_siginfo(&self) -> SignalfdSiginfo { let siginfo = self.to_info(); SignalfdSiginfo { ssi_signo: siginfo.si_signo as _, ssi_errno: siginfo.si_errno, ssi_code: siginfo.si_code, ssi_pid: 0, ssi_uid: 0, ssi_fd: 0, ssi_tid: 0, ssi_band: 0, ssi_overrun: 0, ssi_trapno: 0, ssi_status: 0, ssi_int: 0, ssi_ptr: 0, ssi_utime: 0, ssi_stime: 0, ssi_addr: 0, _pad: [0; 48], } } }