blob: 706bcb8cb206c0cc868dcc6779ca4d49df8317c4 [file] [log] [blame]
//! 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<const N: usize>() -> [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<const N: usize>() -> [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::<KernelSigSet>() * 8);
}
#[test]
fn test_layouts() {
assert!(size_of::<KernelSigSet>() <= size_of::<libc::sigset_t>());
assert!(align_of::<KernelSigSet>() <= align_of::<libc::sigset_t>());
}
/// A bunch of signals for testing.
fn sigs() -> Vec<Signal> {
#[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::<libc::sigset_t>() };
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::<KernelSigSet>(),
)
},
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::<KernelSigSet>(),
)
},
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::<KernelSigSet>(),
)
},
0
);
ru.remove(sig);
assert_ne!(
unsafe {
libc::memcmp(
as_ptr(&lc).cast(),
as_ptr(&ru).cast(),
core::mem::size_of::<KernelSigSet>(),
)
},
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::<KernelSigSet>(),
)
},
0
);
}
}
}