asterinas/kernel/src/syscall/signalfd.rs

351 lines
9.9 KiB
Rust

// 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::{
fmt::Display,
sync::atomic::{AtomicBool, Ordering},
};
use bitflags::bitflags;
use ostd::mm::VmIo;
use super::SyscallReturn;
use crate::{
events::IoEvents,
fs::{
file_handle::FileLike,
file_table::{FdFlags, FileDesc, get_file_fast},
path::RESERVED_MOUNT_ID,
pseudofs::AnonInodeFs,
utils::{CreationFlags, Inode, StatusFlags},
},
prelude::*,
process::{
posix_thread::{AsPosixThread, PosixThread},
signal::{
HandlePendingSignal, PollHandle, Pollable, Poller,
constants::{SIGKILL, SIGSTOP},
sig_mask::{AtomicSigMask, SigMask},
signals::Signal,
},
},
};
/// 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<SyscallReturn> {
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<SyscallReturn> {
debug!(
"fd = {}, mask = {:x}, sizemask = {}, flags = {}",
fd, mask_ptr, sizemask, flags
);
if sizemask != size_of::<SigMask>() {
return Err(Error::with_message(Errno::EINVAL, "invalid mask size"));
}
let mut mask = ctx.user_space().read_val::<SigMask>(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<FileDesc> {
let signal_file = {
let atomic_mask = AtomicSigMask::new(mask);
Arc::new(SignalFile::new(atomic_mask, non_blocking))
};
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<FileDesc> {
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::<SignalFile>()
.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)
}
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,
/// Non-blocking mode flag
non_blocking: AtomicBool,
}
impl SignalFile {
/// Create a new signalfd instance
fn new(mask: AtomicSigMask, non_blocking: bool) -> Self {
Self {
signals_mask: mask,
non_blocking: AtomicBool::new(non_blocking),
}
}
fn mask(&self) -> &AtomicSigMask {
&self.signals_mask
}
fn update_signal_mask(&self, new_mask: SigMask) -> Result<()> {
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, posix_thread: &PosixThread) -> IoEvents {
let mask = self.signals_mask.load(Ordering::Relaxed);
if posix_thread.pending_signals().intersects(mask) {
IoEvents::IN
} else {
IoEvents::empty()
}
}
/// Attempt non-blocking read operation
fn try_read(&self, writer: &mut VmWriter, thread: &PosixThread) -> Result<usize> {
// Mask is inverted to get the signals that are not blocked
let mask = !self.signals_mask.load(Ordering::Relaxed);
let max_signals = writer.avail() / size_of::<SignalfdSiginfo>();
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;
}
None => break,
}
}
if count == 0 {
return_errno!(Errno::EAGAIN);
}
Ok(count * size_of::<SignalfdSiginfo>())
}
}
impl Pollable for SignalFile {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
let current = current_thread!();
let Some(posix_thread) = current.as_posix_thread() else {
return IoEvents::empty();
};
if let Some(poller) = poller {
posix_thread.register_signalfd_poller(poller, mask);
}
self.check_io_events(posix_thread) & mask
}
}
impl FileLike for SignalFile {
fn read(&self, writer: &mut VmWriter) -> Result<usize> {
if writer.avail() < size_of::<SignalfdSiginfo>() {
return_errno_with_message!(Errno::EINVAL, "Buffer too small for siginfo structure");
}
let thread = current_thread!();
let posix_thread = thread
.as_posix_thread()
.ok_or_else(|| Error::with_message(Errno::ESRCH, "Not a POSIX thread"))?;
// Fast path: There are already pending signals or the signalfd is non-blocking.
// So we don't need to create and register the poller.
match self.try_read(writer, posix_thread) {
Err(e) if e.error() == Errno::EAGAIN && !self.is_non_blocking() => {}
res => return res,
}
// Slow path
let mut poller = Poller::new(None);
posix_thread.register_signalfd_poller(poller.as_handle_mut(), IoEvents::IN);
loop {
match self.try_read(writer, posix_thread) {
Err(e) if e.error() == Errno::EAGAIN => poller.wait()?,
res => return res,
}
}
}
fn write(&self, _reader: &mut VmReader) -> Result<usize> {
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 inode(&self) -> &Arc<dyn Inode> {
AnonInodeFs::shared_inode()
}
fn dump_proc_fdinfo(self: Arc<Self>, fd_flags: FdFlags) -> Box<dyn Display> {
struct FdInfo {
flags: u32,
ino: u64,
sigmask: u64,
}
impl Display for FdInfo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(f, "pos:\t{}", 0)?;
writeln!(f, "flags:\t0{:o}", self.flags)?;
// TODO: This should be the mount ID of the pseudo filesystem.
writeln!(f, "mnt_id:\t{}", RESERVED_MOUNT_ID)?;
writeln!(f, "ino:\t{}", self.ino)?;
writeln!(f, "sigmask:\t{:016x}", self.sigmask)
}
}
let mut flags = self.status_flags().bits() | self.access_mode() as u32;
if fd_flags.contains(FdFlags::CLOEXEC) {
flags |= CreationFlags::O_CLOEXEC.bits();
}
Box::new(FdInfo {
flags,
ino: self.inode().ino(),
sigmask: self.mask().load(Ordering::Relaxed).into(),
})
}
}
#[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<dyn Signal> {
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],
}
}
}