//! The [`KernelSigSet`] type. #![allow(unsafe_code)] #![allow(non_camel_case_types)] use crate::backend::c; use crate::signal::Signal; use core::fmt; use linux_raw_sys::general::{kernel_sigset_t, _NSIG}; /// `kernel_sigset_t`—A set of signal numbers, as used by some syscalls. /// /// This is similar to `libc::sigset_t`, but with only enough space for the /// signals currently known to be used by the kernel. libc implementations /// reserve extra space so that if Linux defines new signals in the future /// they can add support without breaking their dynamic linking ABI. Rustix /// doesn't support a dynamic linking ABI, so if we need to increase the /// size of `KernelSigSet` in the future, we can do so. /// /// It's also the case that the last time Linux changed the size of its /// `kernel_sigset_t` was when it added support for POSIX.1b signals in 1999. /// /// `KernelSigSet` is guaranteed to have a subset of the layout of /// `libc::sigset_t`. /// /// libc implementations typically also reserve some signal values for internal /// use. In a process that contains a libc, some unsafe functions invoke /// undefined behavior if passed a `KernelSigSet` that contains one of the /// signals that the libc reserves. #[repr(transparent)] #[derive(Clone)] pub struct KernelSigSet(kernel_sigset_t); impl KernelSigSet { /// Create a new empty `KernelSigSet`. pub const fn empty() -> Self { const fn zeros() -> [c::c_ulong; N] { [0; N] } Self(kernel_sigset_t { sig: zeros() }) } /// Create a new `KernelSigSet` with all signals set. /// /// This includes signals which are typically reserved for libc. pub const fn all() -> Self { const fn ones() -> [c::c_ulong; N] { [!0; N] } Self(kernel_sigset_t { sig: ones() }) } /// Remove all signals. pub fn clear(&mut self) { *self = Self(kernel_sigset_t { sig: Default::default(), }); } /// Insert a signal. pub fn insert(&mut self, sig: Signal) { let sigs_per_elt = core::mem::size_of_val(&self.0.sig[0]) * 8; let raw = (sig.as_raw().wrapping_sub(1)) as usize; self.0.sig[raw / sigs_per_elt] |= 1 << (raw % sigs_per_elt); } /// Insert all signals. pub fn insert_all(&mut self) { self.0.sig.fill(!0); } /// Remove a signal. pub fn remove(&mut self, sig: Signal) { let sigs_per_elt = core::mem::size_of_val(&self.0.sig[0]) * 8; let raw = (sig.as_raw().wrapping_sub(1)) as usize; self.0.sig[raw / sigs_per_elt] &= !(1 << (raw % sigs_per_elt)); } /// Test whether a given signal is present. pub fn contains(&self, sig: Signal) -> bool { let sigs_per_elt = core::mem::size_of_val(&self.0.sig[0]) * 8; let raw = (sig.as_raw().wrapping_sub(1)) as usize; (self.0.sig[raw / sigs_per_elt] & (1 << (raw % sigs_per_elt))) != 0 } } impl Default for KernelSigSet { #[inline] fn default() -> Self { Self::empty() } } impl fmt::Debug for KernelSigSet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = f.debug_set(); // Surprisingly, `_NSIG` is inclusive. for i in 1..=_NSIG { // SAFETY: This value is non-zero, in range, and only used for // debug output. let sig = unsafe { Signal::from_raw_unchecked(i as _) }; if self.contains(sig) { d.entry(&sig); } } d.finish() } } #[cfg(test)] mod tests { use super::*; #[cfg(linux_raw)] use crate::runtime::{KERNEL_SIGRTMAX, KERNEL_SIGRTMIN}; use core::mem::{align_of, size_of}; #[test] fn test_assumptions() { #[cfg(linux_raw)] assert!(KERNEL_SIGRTMAX as usize - 1 < size_of::() * 8); } #[test] fn test_layouts() { assert!(size_of::() <= size_of::()); assert!(align_of::() <= align_of::()); } /// A bunch of signals for testing. fn sigs() -> Vec { #[allow(unused_mut)] let mut sigs = vec![ Signal::HUP, Signal::INT, Signal::QUIT, Signal::ILL, Signal::TRAP, Signal::ABORT, Signal::BUS, Signal::FPE, Signal::KILL, Signal::USR1, Signal::SEGV, Signal::USR2, Signal::PIPE, Signal::ALARM, Signal::TERM, Signal::CHILD, Signal::CONT, Signal::STOP, Signal::TSTP, Signal::TTIN, Signal::TTOU, Signal::URG, Signal::XCPU, Signal::XFSZ, Signal::VTALARM, Signal::PROF, Signal::WINCH, Signal::SYS, unsafe { Signal::from_raw_unchecked(libc::SIGRTMIN()) }, unsafe { Signal::from_raw_unchecked(libc::SIGRTMIN() + 7) }, unsafe { Signal::from_raw_unchecked(libc::SIGRTMAX()) }, ]; #[cfg(linux_raw)] { sigs.push(unsafe { Signal::from_raw_unchecked(KERNEL_SIGRTMIN) }); sigs.push(unsafe { Signal::from_raw_unchecked(KERNEL_SIGRTMIN + 7) }); sigs.push(unsafe { Signal::from_raw_unchecked(KERNEL_SIGRTMAX) }); } sigs } /// A bunch of non-reserved signals for testing. fn libc_sigs() -> [Signal; 31] { [ Signal::HUP, Signal::INT, Signal::QUIT, Signal::ILL, Signal::TRAP, Signal::ABORT, Signal::BUS, Signal::FPE, Signal::KILL, Signal::USR1, Signal::SEGV, Signal::USR2, Signal::PIPE, Signal::ALARM, Signal::TERM, Signal::CHILD, Signal::CONT, Signal::STOP, Signal::TSTP, Signal::TTIN, Signal::TTOU, Signal::URG, Signal::XCPU, Signal::XFSZ, Signal::VTALARM, Signal::PROF, Signal::WINCH, Signal::SYS, unsafe { Signal::from_raw_unchecked(libc::SIGRTMIN()) }, unsafe { Signal::from_raw_unchecked(libc::SIGRTMIN() + 7) }, unsafe { Signal::from_raw_unchecked(libc::SIGRTMAX()) }, ] } #[test] fn test_ops_plain() { for sig in sigs() { let mut set = KernelSigSet::empty(); for sig in sigs() { assert!(!set.contains(sig)); } set.insert(sig); assert!(set.contains(sig)); for sig in sigs().iter().filter(|s| **s != sig) { assert!(!set.contains(*sig)); } set.remove(sig); for sig in sigs() { assert!(!set.contains(sig)); } } } #[test] fn test_clear() { let mut set = KernelSigSet::empty(); for sig in sigs() { set.insert(sig); } set.clear(); for sig in sigs() { assert!(!set.contains(sig)); } } // io_uring libraries assume that libc's `sigset_t` matches the layout // of the Linux kernel's `kernel_sigset_t`. Test that rustix's layout // matches as well. #[test] fn test_libc_layout_compatibility() { use crate::utils::as_ptr; let mut lc = unsafe { core::mem::zeroed::() }; let mut ru = KernelSigSet::empty(); let r = unsafe { libc::sigemptyset(&mut lc) }; assert_eq!(r, 0); assert_eq!( unsafe { libc::memcmp( as_ptr(&lc).cast(), as_ptr(&ru).cast(), core::mem::size_of::(), ) }, 0 ); for sig in libc_sigs() { ru.insert(sig); assert_ne!( unsafe { libc::memcmp( as_ptr(&lc).cast(), as_ptr(&ru).cast(), core::mem::size_of::(), ) }, 0 ); let r = unsafe { libc::sigaddset(&mut lc, sig.as_raw()) }; assert_eq!(r, 0); assert_eq!( unsafe { libc::memcmp( as_ptr(&lc).cast(), as_ptr(&ru).cast(), core::mem::size_of::(), ) }, 0 ); ru.remove(sig); assert_ne!( unsafe { libc::memcmp( as_ptr(&lc).cast(), as_ptr(&ru).cast(), core::mem::size_of::(), ) }, 0 ); let r = unsafe { libc::sigdelset(&mut lc, sig.as_raw()) }; assert_eq!(r, 0); assert_eq!( unsafe { libc::memcmp( as_ptr(&lc).cast(), as_ptr(&ru).cast(), core::mem::size_of::(), ) }, 0 ); } } }