use std::ops::{
BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Sub, SubAssign,
};
use libc::c_int;
use nix::sys::signal::Signal;
use serde::{Serialize, Serializer};
#[expect(non_camel_case_types)]
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
pub type sigset_t = u128;
#[expect(non_camel_case_types)]
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
)))]
pub type sigset_t = u64;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SydSigSet(pub sigset_t);
impl BitOr for SydSigSet {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
SydSigSet(self.0 | rhs.0)
}
}
impl BitOrAssign for SydSigSet {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl BitAnd for SydSigSet {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
SydSigSet(self.0 & rhs.0)
}
}
impl BitAndAssign for SydSigSet {
fn bitand_assign(&mut self, rhs: Self) {
self.0 &= rhs.0;
}
}
impl BitXor for SydSigSet {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
SydSigSet(self.0 ^ rhs.0)
}
}
impl BitXorAssign for SydSigSet {
fn bitxor_assign(&mut self, rhs: Self) {
self.0 ^= rhs.0;
}
}
impl Not for SydSigSet {
type Output = Self;
fn not(self) -> Self::Output {
SydSigSet(!self.0)
}
}
impl Sub for SydSigSet {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
SydSigSet(self.0 & !rhs.0)
}
}
impl SubAssign for SydSigSet {
fn sub_assign(&mut self, rhs: Self) {
self.0 &= !rhs.0;
}
}
impl std::fmt::Display for SydSigSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let max_signal = libc::SIGRTMAX();
#[expect(clippy::cast_sign_loss)]
let mut signals = Vec::with_capacity(max_signal as usize);
for sig_num in 1..max_signal {
if self.contains(sig_num) {
signals.push(sig_num.to_string());
}
}
if !signals.is_empty() {
write!(f, "{}", signals.join(", "))
} else {
write!(f, "?")
}
}
}
impl SydSigSet {
pub fn new(mask: sigset_t) -> Self {
Self(mask)
}
pub fn is_empty(&self) -> bool {
self.0 == 0
}
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_sign_loss)]
pub fn contains(&self, sig: c_int) -> bool {
if sig < 1 {
return false;
}
let bit = (sig - 1) as sigset_t;
(self.0 & (1 << bit)) != 0
}
pub fn intersects(&self, other: Self) -> bool {
(self.0 & other.0) != 0
}
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_sign_loss)]
pub fn add(&mut self, sig: c_int) {
if sig < 1 {
return; }
let bit = (sig - 1) as sigset_t;
self.0 |= 1 << bit;
}
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_sign_loss)]
pub fn del(&mut self, sig: c_int) {
if sig < 1 {
return; }
let bit = (sig - 1) as sigset_t;
self.0 &= !(1 << bit);
}
pub fn add_set(&mut self, set: Self) {
self.0 |= set.0;
}
pub fn del_set(&mut self, set: Self) {
self.0 &= !set.0;
}
}
impl Serialize for SydSigSet {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let max_signal = libc::SIGRTMAX();
let mut signals = Vec::new();
for sig_num in 1..max_signal {
if self.contains(sig_num) {
let sig_name = Signal::try_from(sig_num)
.map(|sig| sig.to_string())
.unwrap_or_else(|_| format!("SIG{sig_num}"));
signals.push(sig_name);
}
}
signals.serialize(serializer)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn max_signal() -> c_int {
libc::SIGRTMAX()
}
#[test]
fn test_sigset_empty_set() {
let set = SydSigSet::new(0);
for sig_num in 1..max_signal() {
assert!(
!set.contains(sig_num),
"Empty set should not contain any signal"
);
}
let empty2 = SydSigSet::new(0);
assert!(
!set.intersects(empty2),
"Empty set should not intersect with another empty set"
);
}
#[test]
fn test_sigset_single_signal() {
let sigalrm = libc::SIGALRM; let mask = (1 as sigset_t) << (sigalrm - 1);
let set = SydSigSet::new(mask);
assert!(set.contains(sigalrm), "Set should contain SIGALRM");
for sig_num in 1..max_signal() {
if sig_num != sigalrm {
assert!(!set.contains(sig_num), "Only SIGALRM should be set");
}
}
let empty = SydSigSet::new(0);
assert!(
!set.intersects(empty),
"Single-signal set should not intersect an empty set"
);
assert!(set.intersects(set), "Set should intersect with itself");
}
#[test]
fn test_sigset_multiple_signals() {
let signals = [libc::SIGINT, libc::SIGALRM, libc::SIGTERM]; let mut mask: sigset_t = 0;
for &sig in &signals {
mask |= (1 as sigset_t) << ((sig - 1) as sigset_t);
}
let set = SydSigSet::new(mask);
for &sig in &signals {
assert!(set.contains(sig), "Set should contain signal {}", sig);
}
for sig_num in 1..max_signal() {
if !signals.contains(&sig_num) {
assert!(!set.contains(sig_num), "Only INT, ALRM, TERM should be set");
}
}
let single = SydSigSet::new((1 as sigset_t) << ((libc::SIGINT - 1) as sigset_t));
assert!(
set.intersects(single),
"Should intersect since both contain SIGINT"
);
let unrelated_mask = (1 as sigset_t) << ((libc::SIGHUP - 1) as sigset_t); let unrelated_set = SydSigSet::new(unrelated_mask);
if !signals.contains(&libc::SIGHUP) {
assert!(
!set.intersects(unrelated_set),
"Should not intersect if no signals in common"
);
}
let mut dynamic_set = SydSigSet::new(0);
for &sig in &signals {
dynamic_set.add(sig);
assert!(
dynamic_set.contains(sig),
"Signal {} should now be contained",
sig
);
}
dynamic_set.del(libc::SIGALRM);
assert!(
!dynamic_set.contains(libc::SIGALRM),
"SIGALRM should be removed"
);
assert!(dynamic_set.contains(libc::SIGINT), "Other signals remain");
assert!(dynamic_set.contains(libc::SIGTERM), "Other signals remain");
}
#[test]
fn test_sigset_all_signals() {
let max_sig = max_signal();
let mut mask: sigset_t = 0;
for sig_num in 1..max_sig {
mask |= (1 as sigset_t) << ((sig_num - 1) as sigset_t);
}
let set = SydSigSet::new(mask);
for sig_num in 1..max_sig {
assert!(set.contains(sig_num), "All signals should be contained");
}
let mut copy_set = set;
copy_set.del(libc::SIGINT);
assert!(
!copy_set.contains(libc::SIGINT),
"SIGINT should be removed from the full set"
);
for sig_num in 1..max_sig {
if sig_num != libc::SIGINT {
assert!(
copy_set.contains(sig_num),
"All others should still be present"
);
}
}
let single_set = SydSigSet::new((1 as sigset_t) << ((libc::SIGTERM - 1) as sigset_t));
assert!(
set.intersects(single_set),
"Full set intersects with any non-empty set"
);
}
#[test]
fn test_sigset_bits_beyond_rtm() {
let max_sig = max_signal();
let mut mask: sigset_t = 0;
let bits = std::mem::size_of::<sigset_t>() * 8;
for i in 0..bits {
mask |= (1 as sigset_t) << i;
}
let set = SydSigSet::new(mask);
for sig_num in 1..max_sig {
assert!(
set.contains(sig_num),
"All signals up to SIGRTMAX should be contained"
);
}
}
#[test]
fn test_sigset_invalid_signals() {
let mut set = SydSigSet::new(0);
set.add(0);
set.add(-1);
assert!(!set.contains(0), "Invalid signal should not be contained");
assert!(!set.contains(-1), "Invalid signal should not be contained");
assert!(!set.contains(1), "We never added a valid signal");
set.del(0);
set.del(-1);
assert!(
!set.contains(1),
"No signals should be added or removed by invalid ops"
);
}
#[test]
fn test_sigset_intersects() {
let set_a_mask = ((1 as sigset_t) << ((libc::SIGINT - 1) as sigset_t))
| ((1 as sigset_t) << ((libc::SIGALRM - 1) as sigset_t));
let set_b_mask = ((1 as sigset_t) << ((libc::SIGALRM - 1) as sigset_t))
| ((1 as sigset_t) << ((libc::SIGTERM - 1) as sigset_t));
let set_a = SydSigSet::new(set_a_mask);
let set_b = SydSigSet::new(set_b_mask);
assert!(set_a.intersects(set_b), "Sets should intersect on SIGALRM");
let set_c = SydSigSet::new((1 as sigset_t) << ((libc::SIGHUP - 1) as sigset_t));
if libc::SIGHUP != libc::SIGINT
&& libc::SIGHUP != libc::SIGALRM
&& libc::SIGHUP != libc::SIGTERM
{
assert!(
!set_a.intersects(set_c),
"A and C should not intersect if distinct signals"
);
assert!(
!set_b.intersects(set_c),
"B and C should not intersect if distinct signals"
);
}
}
}