Repository: bytecodealliance/rustix (language: rust)

Issue:
`Signal` should support arbitrary signals
The `Signal` enum is missing the real-time signals, from `SIGRTMIN` to `SIGRTMAX`.

Handling these will be interesting, because glibc reserves the first two of them, and does so by changing its exposed value of `SIGRTMIN`.

I would suggest exposing the same `SIGRTMIN` that glibc does (34 rather than 32), and additionally exposing enum values for the two reserved signal numbers, with names that clearly indicate that they're reserved by libc.

That way, when people port from C to Rust, they won't get surprised by the difference.


Discussion / hints:
A complication is that glibc does not expose the values of `SIGRTMIN` or `SIGRTMAX` in its ABI. Even the number of signals it reserves is not exposed. The number of reserved signals has changed in the past and could theoretically change again in the future.

Perhaps we should make functions like [`kill_process`](rustix::process::kill_process) unsafe. That way we could allow `Signal` to hold any `SIGRTMIN+n` signal and just say it's UB if you ever use a reserved signal value.

Another would be to make `Signal::from_raw` unsafe, and make it UB to construct a `Signal` with a reserved signal value.
I think at this point it is safe to assume that the number of reserved signals won't change anymore; it has been the same since the move from libc5 to libc6, and there will almost certainly never be a libc7. If that *did* change, far too much would break.

If you prefer to not bake in that assumption, you could expose the real SIGRTMIN, and let applications handle avoiding the reserved signals. 

Either way, I don't think you need to make the possibility of sending *those* signals any more unsafe than any other signal; sending any signal an application doesn't expect can cause problems. So if you want to make something unsafe, I would suggest kill_process rather than the creation of the signal number.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 2d05fba54..5304c6f6e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -85,7 +85,7 @@ name = "mod"
 harness = false
 
 [package.metadata.docs.rs]
-features = ["all-apis"]
+features = ["all-apis", "use-libc-sigrt"]
 targets = [
     "x86_64-unknown-linux-gnu",
     "i686-unknown-linux-gnu",
@@ -195,6 +195,9 @@ all-apis = [
     "time",
 ]
 
+# Enable `Signal::rt` and related features, which depend on libc.
+use-libc-sigrt = []
+
 # When using the linux_raw backend, should we use libc for reading the aux
 # vectors, instead of reading them ourselves from /proc/self/auxv?
 use-libc-auxv = []
diff --git a/examples/kq.rs b/examples/kq.rs
index 04306224b..a2209ce9b 100644
--- a/examples/kq.rs
+++ b/examples/kq.rs
@@ -22,7 +22,7 @@ fn main() -> std::io::Result<()> {
         #[cfg(feature = "process")]
         Event::new(
             EventFilter::Signal {
-                signal: rustix::process::Signal::Info,
+                signal: rustix::process::Signal::INFO,
                 times: 0,
             },
             EventFlags::ADD,
diff --git a/src/backend/libc/process/syscalls.rs b/src/backend/libc/process/syscalls.rs
index 32d5ccaf0..1faaabf56 100644
--- a/src/backend/libc/process/syscalls.rs
+++ b/src/backend/libc/process/syscalls.rs
@@ -523,7 +523,7 @@ pub(crate) fn setsid() -> io::Result<Pid> {
 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
 #[inline]
 pub(crate) fn kill_process(pid: Pid, sig: Signal) -> io::Result<()> {
-    unsafe { ret(c::kill(pid.as_raw_nonzero().get(), sig as i32)) }
+    unsafe { ret(c::kill(pid.as_raw_nonzero().get(), sig.as_raw())) }
 }
 
 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
@@ -532,7 +532,7 @@ pub(crate) fn kill_process_group(pid: Pid, sig: Signal) -> io::Result<()> {
     unsafe {
         ret(c::kill(
             pid.as_raw_nonzero().get().wrapping_neg(),
-            sig as i32,
+            sig.as_raw(),
         ))
     }
 }
@@ -540,7 +540,7 @@ pub(crate) fn kill_process_group(pid: Pid, sig: Signal) -> io::Result<()> {
 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
 #[inline]
 pub(crate) fn kill_current_process_group(sig: Signal) -> io::Result<()> {
-    unsafe { ret(c::kill(0, sig as i32)) }
+    unsafe { ret(c::kill(0, sig.as_raw())) }
 }
 
 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
@@ -600,7 +600,7 @@ pub(crate) fn pidfd_send_signal(pidfd: BorrowedFd<'_>, sig: Signal) -> io::Resul
     unsafe {
         ret(pidfd_send_signal(
             borrowed_fd(pidfd),
-            sig as c::c_int,
+            sig.as_raw(),
             core::ptr::null(),
             0,
         ))
diff --git a/src/backend/linux_raw/conv.rs b/src/backend/linux_raw/conv.rs
index 26f7a2e2b..5459147ee 100644
--- a/src/backend/linux_raw/conv.rs
+++ b/src/backend/linux_raw/conv.rs
@@ -709,7 +709,7 @@ pub(super) fn negative_pid<'a, Num: ArgNumber>(pid: Pid) -> ArgReg<'a, Num> {
 impl<'a, Num: ArgNumber> From<Signal> for ArgReg<'a, Num> {
     #[inline]
     fn from(sig: Signal) -> Self {
-        pass_usize(sig as usize)
+        pass_usize(sig.as_raw() as usize)
     }
 }
 
diff --git a/src/backend/linux_raw/runtime/syscalls.rs b/src/backend/linux_raw/runtime/syscalls.rs
index eac0da324..d706951de 100644
--- a/src/backend/linux_raw/runtime/syscalls.rs
+++ b/src/backend/linux_raw/runtime/syscalls.rs
@@ -215,16 +215,13 @@ pub(crate) fn sigsuspend(set: &Sigset) -> io::Result<()> {
 #[inline]
 pub(crate) fn sigwait(set: &Sigset) -> io::Result<Signal> {
     unsafe {
-        match Signal::from_raw(ret_c_int(syscall_readonly!(
+        Ok(Signal::from_raw_unchecked(ret_c_int(syscall_readonly!(
             __NR_rt_sigtimedwait,
             by_ref(set),
             zero(),
             zero(),
             size_of::<kernel_sigset_t, _>()
-        ))?) {
-            Some(signum) => Ok(signum),
-            None => Err(io::Errno::NOTSUP),
-        }
+        ))?))
     }
 }
 
diff --git a/src/event/kqueue.rs b/src/event/kqueue.rs
index 36b5f82b4..114719bf1 100644
--- a/src/event/kqueue.rs
+++ b/src/event/kqueue.rs
@@ -34,7 +34,9 @@ impl Event {
             EventFilter::Proc { pid, flags } => {
                 (Pid::as_raw(Some(pid)) as _, 0, c::EVFILT_PROC, flags.bits())
             }
-            EventFilter::Signal { signal, times: _ } => (signal as _, 0, c::EVFILT_SIGNAL, 0),
+            EventFilter::Signal { signal, times: _ } => {
+                (signal.as_raw() as _, 0, c::EVFILT_SIGNAL, 0)
+            }
             EventFilter::Timer { ident, timer } => {
                 #[cfg(any(apple, target_os = "freebsd", target_os = "netbsd"))]
                 let (data, fflags) = match timer {
@@ -118,7 +120,8 @@ impl Event {
                 flags: ProcessEvents::from_bits_retain(self.inner.fflags),
             },
             c::EVFILT_SIGNAL => EventFilter::Signal {
-                signal: Signal::from_raw(self.inner.ident as _).unwrap(),
+                // SAFETY: `EventFilter::new` requires a valid `Signal`.
+                signal: unsafe { Signal::from_raw_unchecked(self.inner.ident as _) },
                 times: self.inner.data as _,
             },
             c::EVFILT_TIMER => EventFilter::Timer {
diff --git a/src/process/exit.rs b/src/process/exit.rs
index f48d5e9b2..e882da14f 100644
--- a/src/process/exit.rs
+++ b/src/process/exit.rs
@@ -24,13 +24,13 @@ pub const EXIT_SUCCESS: i32 = backend::c::EXIT_SUCCESS;
 /// [Linux]: https://man7.org/linux/man-pages/man3/exit.3.html
 pub const EXIT_FAILURE: i32 = backend::c::EXIT_FAILURE;
 
-/// The exit status used by a process terminated with a [`Signal::Abort`]
+/// The exit status used by a process terminated with a [`Signal::ABORT`]
 /// signal.
 ///
 /// # References
 ///  - [Linux]
 ///
 /// [Linux]: https://tldp.org/LDP/abs/html/exitcodes.html
-/// [`Signal::Abort`]: crate::process::Signal::Abort
+/// [`Signal::ABORT`]: crate::process::Signal::ABORT
 #[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
 pub const EXIT_SIGNALED_SIGABRT: i32 = backend::c::EXIT_SIGNALED_SIGABRT;
diff --git a/src/process/prctl.rs b/src/process/prctl.rs
index d8cc2a1af..3b5b1a0a0 100644
--- a/src/process/prctl.rs
+++ b/src/process/prctl.rs
@@ -6,6 +6,7 @@
 #![allow(unsafe_code)]
 
 use core::mem::size_of;
+use core::num::NonZeroI32;
 use core::ptr::{null, null_mut, NonNull};
 
 use bitflags::bitflags;
@@ -36,7 +37,18 @@ const PR_GET_PDEATHSIG: c_int = 2;
 #[inline]
 #[doc(alias = "PR_GET_PDEATHSIG")]
 pub fn parent_process_death_signal() -> io::Result<Option<Signal>> {
-    unsafe { prctl_get_at_arg2_optional::<c_int>(PR_GET_PDEATHSIG) }.map(Signal::from_raw)
+    let raw = unsafe { prctl_get_at_arg2_optional::<c_int>(PR_GET_PDEATHSIG)? };
+    if let Some(non_zero) = NonZeroI32::new(raw) {
+        // SAFETY: The only way to get a libc-reserved signal number in
+        // here would be to do something equivalent to
+        // `set_parent_process_death_signal`, but that would have required
+        // using a `Signal` with a libc-reserved value.
+        Ok(Some(unsafe {
+            Signal::from_raw_nonzero_unchecked(non_zero)
+        }))
+    } else {
+        Ok(None)
+    }
 }
 
 const PR_SET_PDEATHSIG: c_int = 1;
@@ -52,7 +64,7 @@ const PR_SET_PDEATHSIG: c_int = 1;
 #[inline]
 #[doc(alias = "PR_SET_PDEATHSIG")]
 pub fn set_parent_process_death_signal(signal: Option<Signal>) -> io::Result<()> {
-    let signal = signal.map_or(0_usize, |signal| signal as usize);
+    let signal = signal.map_or(0_usize, |signal| signal.as_raw() as usize);
     unsafe { prctl_2args(PR_SET_PDEATHSIG, signal as *mut _) }.map(|_r| ())
 }
 
@@ -436,7 +448,7 @@ const PR_TSC_SIGSEGV: u32 = 2;
 pub enum TimeStampCounterReadability {
     /// Allow the use of the timestamp counter.
     Readable = PR_TSC_ENABLE,
-    /// Throw a [`Signal::Segv`] signal instead of reading the TSC.
+    /// Throw a [`Signal::SEGV`] signal instead of reading the TSC.
     RaiseSIGSEGV = PR_TSC_SIGSEGV,
 }
 
diff --git a/src/process/procctl.rs b/src/process/procctl.rs
index 3b549fc4c..8c38ef241 100644
--- a/src/process/procctl.rs
+++ b/src/process/procctl.rs
@@ -8,6 +8,7 @@
 #[cfg(feature = "alloc")]
 use alloc::{vec, vec::Vec};
 use core::mem::MaybeUninit;
+use core::num::NonZeroI32;
 use core::ptr;
 
 use bitflags::bitflags;
@@ -92,7 +93,18 @@ const PROC_PDEATHSIG_STATUS: c_int = 12;
 /// [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,…)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
 #[inline]
 pub fn parent_process_death_signal() -> io::Result<Option<Signal>> {
-    unsafe { procctl_get_optional::<c_int>(PROC_PDEATHSIG_STATUS, None) }.map(Signal::from_raw)
+    let raw = unsafe { procctl_get_optional::<c_int>(PROC_PDEATHSIG_STATUS, None) }?;
+    if let Some(non_zero) = NonZeroI32::new(raw) {
+        // SAFETY: The only way to get a libc-reserved signal number in
+        // here would be to do something equivalent to
+        // `set_parent_process_death_signal`, but that would have required
+        // using a `Signal` with a libc-reserved value.
+        Ok(Some(unsafe {
+            Signal::from_raw_nonzero_unchecked(non_zero)
+        }))
+    } else {
+        Ok(None)
+    }
 }
 
 const PROC_PDEATHSIG_CTL: c_int = 11;
@@ -107,7 +119,7 @@ const PROC_PDEATHSIG_CTL: c_int = 11;
 /// [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,…)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
 #[inline]
 pub fn set_parent_process_death_signal(signal: Option<Signal>) -> io::Result<()> {
-    let signal = signal.map_or(0, |signal| signal as c_int);
+    let signal = signal.map_or(0, |signal| signal.as_raw());
     unsafe { procctl_set::<c_int>(PROC_PDEATHSIG_CTL, None, &signal) }
 }
 
@@ -419,7 +431,7 @@ pub fn reaper_kill(
     flags.set(KillFlags::CHILDREN, direct_children);
     flags.set(KillFlags::SUBTREE, subtree.is_some());
     let mut req = procctl_reaper_kill {
-        rk_sig: signal as c_int,
+        rk_sig: signal.as_raw(),
         rk_flags: flags.bits(),
         rk_subtree: subtree.map(|p| p.as_raw_nonzero().into()).unwrap_or(0),
         rk_killed: 0,
diff --git a/src/pty.rs b/src/pty.rs
index b7d1fdd6a..77c9afa25 100644
--- a/src/pty.rs
+++ b/src/pty.rs
@@ -156,7 +156,7 @@ pub fn unlockpt<Fd: AsFd>(fd: Fd) -> io::Result<()> {
 ///
 /// On Linux, calling this function has no effect, as the kernel is expected to
 /// grant the appropriate access. On all other platforms, this function has
-/// unspecified behavior if the calling process has a [`Signal::Child`] signal
+/// unspecified behavior if the calling process has a [`Signal::CHILD`] signal
 /// handler installed.
 ///
 /// # References
@@ -167,7 +167,7 @@ pub fn unlockpt<Fd: AsFd>(fd: Fd) -> io::Result<()> {
 /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/grantpt.html
 /// [Linux]: https://man7.org/linux/man-pages/man3/grantpt.3.html
 /// [glibc]: https://sourceware.org/glibc/manual/latest/html_node/Allocation.html#index-grantpt
-/// [`Signal::Child`]: crate::process::Signal::Child
+/// [`Signal::CHILD`]: crate::process::Signal::CHILD
 #[inline]
 pub fn grantpt<Fd: AsFd>(fd: Fd) -> io::Result<()> {
     #[cfg(not(linux_kernel))]
diff --git a/src/runtime.rs b/src/runtime.rs
index a690dde7d..d15c97b15 100644
--- a/src/runtime.rs
+++ b/src/runtime.rs
@@ -5,7 +5,10 @@
 //!
 //!  - Some of the functions in this module cannot be used in a process which
 //!    also has a libc present. This can be true even for functions that have
-//!    the same name as a libc function that Rust code can use.
+//!    the same name as a libc function that Rust code can use. Such functions
+//!    are not marked `unsafe` (unless they are unsafe for other reasons), even
+//!    though they invoke Undefined Behavior if called in a process which has a
+//!    libc present.
 //!
 //!  - Some of the functions in this module don't behave exactly the same way
 //!    as functions in libc with similar names. Sometimes information about the
@@ -62,7 +65,13 @@ pub type Sigaction = linux_raw_sys::general::kernel_sigaction;
 #[cfg(linux_raw)]
 pub type Stack = linux_raw_sys::general::stack_t;
 
-/// `sigset_t`
+/// `sigset_t`.
+///
+/// Undefined behavior could happen in some functions if `Sigset` ever
+/// contains signal numbers in the range from
+/// `linux_raw_sys::general::SIGRTMIN` to what the libc thinks `SIGRTMIN` is.
+/// Unless you are implementing the libc. Which you may indeed be doing, if
+/// you're reading this.
 #[cfg(linux_raw)]
 pub type Sigset = linux_raw_sys::general::kernel_sigset_t;
 
@@ -560,26 +569,31 @@ pub fn linux_secure() -> bool {
 ///
 /// This is not identical to `brk` in libc. libc `brk` may have bookkeeping
 /// that needs to be kept up to date that this doesn't keep up to date, so
-/// don't use it unless you are implementing libc.
+/// don't use it unless you know your code won't share a process with a libc
+/// (perhaps because you yourself are implementing a libc).
 #[cfg(linux_raw)]
 #[inline]
 pub unsafe fn brk(addr: *mut c_void) -> io::Result<*mut c_void> {
     backend::runtime::syscalls::brk(addr)
 }
 
-/// `__SIGRTMIN`—The start of the realtime signal range.
+/// `SIGRTMIN`—The start of the raw OS “real-time” signal range.
 ///
 /// This is the raw `SIGRTMIN` value from the OS, which is not the same as the
-/// `SIGRTMIN` macro provided by libc. Don't use this unless you are
-/// implementing libc.
+/// `SIGRTMIN` macro provided by libc. Don't use this unless you know your code
+/// won't share a process with a libc (perhaps because you yourself are
+/// implementing a libc).
+///
+/// See [`sigrt`] for a convenient way to construct `SIGRTMIN + n` values.
 #[cfg(linux_raw)]
 pub const SIGRTMIN: u32 = linux_raw_sys::general::SIGRTMIN;
 
-/// `__SIGRTMAX`—The last of the realtime signal range.
+/// `SIGRTMAX`—The last of the raw OS “real-time” signal range.
 ///
 /// This is the raw `SIGRTMAX` value from the OS, which is not the same as the
-/// `SIGRTMAX` macro provided by libc. Don't use this unless you are
-/// implementing libc.
+/// `SIGRTMAX` macro provided by libc. Don't use this unless you know your code
+/// won't share a process with a libc (perhaps because you yourself are
+/// implementing a libc).
 #[cfg(linux_raw)]
 pub const SIGRTMAX: u32 = {
     // Use the actual `SIGRTMAX` value on platforms which define it.
@@ -604,3 +618,24 @@ pub const SIGRTMAX: u32 = {
         linux_raw_sys::general::_NSIG - 1
     }
 };
+
+/// Return a [`Signal`] corresponding to `SIGRTMIN + n`.
+///
+/// This is similar to [`Signal::rt`], but uses the raw OS `SIGRTMIN` value
+/// instead of the libc `SIGRTMIN` value. Don't use this unless you know your
+/// code won't share a process with a libc (perhaps because you yourself are
+/// implementing a libc).
+#[cfg(linux_raw)]
+#[doc(alias = "SIGRTMIN")]
+pub fn sigrt(n: u32) -> Option<Signal> {
+    let sig = SIGRTMIN.wrapping_add(n);
+    if (SIGRTMIN..=SIGRTMAX).contains(&sig) {
+        // SAFETY: We've checked that `sig` is in the expected range. It could
+        // still conflict with libc's reserved values, however users of the
+        // `runtime` module here must already know that there's no other libc
+        // to conflict with.
+        Some(unsafe { Signal::from_raw_unchecked(sig as i32) })
+    } else {
+        None
+    }
+}
diff --git a/src/signal.rs b/src/signal.rs
index b652b1a0f..5f1943bd4 100644
--- a/src/signal.rs
+++ b/src/signal.rs
@@ -1,5 +1,15 @@
+//! Signal numbers.
+//!
+//! # Safety
+//!
+//! Some signal numbers are reserved by the libc.
+//! [`Signal::from_raw_unchecked`] allows constructing `Signal` values with
+//! arbitrary values. Users must avoid using these values to send or
+//! consume signals in any way.
+#![allow(unsafe_code)]
+
 use crate::backend::c;
-use crate::ffi;
+use core::num::NonZeroI32;
 
 /// A signal number for use with [`kill_process`], [`kill_process_group`], and
 /// [`kill_current_process_group`].
@@ -8,43 +18,51 @@ use crate::ffi;
 /// [`kill_process_group`]: crate::process::kill_process_group
 /// [`kill_current_process_group`]: crate::process::kill_current_process_group
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
-#[repr(i32)]
-pub enum Signal {
+#[repr(transparent)]
+pub struct Signal(NonZeroI32);
+
+/// Signal constants.
+///
+/// To construct `SIGRTMIN + n` “real-time” signal values, use
+/// [`Signal::rt`].
+// SAFETY: The libc-defined signal values are all non-zero.
+#[rustfmt::skip]
+impl Signal {
     /// `SIGHUP`
-    Hup = c::SIGHUP,
+    pub const HUP: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGHUP) });
     /// `SIGINT`
-    Int = c::SIGINT,
+    pub const INT: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGINT) });
     /// `SIGQUIT`
-    Quit = c::SIGQUIT,
+    pub const QUIT: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGQUIT) });
     /// `SIGILL`
-    Ill = c::SIGILL,
+    pub const ILL: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGILL) });
     /// `SIGTRAP`
-    Trap = c::SIGTRAP,
+    pub const TRAP: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGTRAP) });
     /// `SIGABRT`, aka `SIGIOT`
     #[doc(alias = "Iot")]
     #[doc(alias = "Abrt")]
-    Abort = c::SIGABRT,
+    pub const ABORT: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGABRT) });
     /// `SIGBUS`
-    Bus = c::SIGBUS,
+    pub const BUS: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGBUS) });
     /// `SIGFPE`
-    Fpe = c::SIGFPE,
+    pub const FPE: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGFPE) });
     /// `SIGKILL`
-    Kill = c::SIGKILL,
+    pub const KILL: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGKILL) });
     /// `SIGUSR1`
     #[cfg(not(target_os = "vita"))]
-    Usr1 = c::SIGUSR1,
+    pub const USR1: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGUSR1) });
     /// `SIGSEGV`
-    Segv = c::SIGSEGV,
+    pub const SEGV: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGSEGV) });
     /// `SIGUSR2`
     #[cfg(not(target_os = "vita"))]
-    Usr2 = c::SIGUSR2,
+    pub const USR2: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGUSR2) });
     /// `SIGPIPE`
-    Pipe = c::SIGPIPE,
+    pub const PIPE: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGPIPE) });
     /// `SIGALRM`
     #[doc(alias = "Alrm")]
-    Alarm = c::SIGALRM,
+    pub const ALARM: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGALRM) });
     /// `SIGTERM`
-    Term = c::SIGTERM,
+    pub const TERM: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGTERM) });
     /// `SIGSTKFLT`
     #[cfg(not(any(
         bsd,
@@ -66,56 +84,56 @@ pub enum Signal {
             ),
         )
     )))]
-    Stkflt = c::SIGSTKFLT,
+    pub const STKFLT: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGSTKFLT) });
     /// `SIGCHLD`
     #[cfg(not(target_os = "vita"))]
     #[doc(alias = "Chld")]
-    Child = c::SIGCHLD,
+    pub const CHILD: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGCHLD) });
     /// `SIGCONT`
     #[cfg(not(target_os = "vita"))]
-    Cont = c::SIGCONT,
+    pub const CONT: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGCONT) });
     /// `SIGSTOP`
     #[cfg(not(target_os = "vita"))]
-    Stop = c::SIGSTOP,
+    pub const STOP: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGSTOP) });
     /// `SIGTSTP`
     #[cfg(not(target_os = "vita"))]
-    Tstp = c::SIGTSTP,
+    pub const TSTP: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGTSTP) });
     /// `SIGTTIN`
     #[cfg(not(target_os = "vita"))]
-    Ttin = c::SIGTTIN,
+    pub const TTIN: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGTTIN) });
     /// `SIGTTOU`
     #[cfg(not(target_os = "vita"))]
-    Ttou = c::SIGTTOU,
+    pub const TTOU: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGTTOU) });
     /// `SIGURG`
     #[cfg(not(target_os = "vita"))]
-    Urg = c::SIGURG,
+    pub const URG: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGURG) });
     /// `SIGXCPU`
     #[cfg(not(target_os = "vita"))]
-    Xcpu = c::SIGXCPU,
+    pub const XCPU: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGXCPU) });
     /// `SIGXFSZ`
     #[cfg(not(target_os = "vita"))]
-    Xfsz = c::SIGXFSZ,
+    pub const XFSZ: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGXFSZ) });
     /// `SIGVTALRM`
     #[cfg(not(target_os = "vita"))]
     #[doc(alias = "Vtalrm")]
-    Vtalarm = c::SIGVTALRM,
+    pub const VTALARM: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGVTALRM) });
     /// `SIGPROF`
     #[cfg(not(target_os = "vita"))]
-    Prof = c::SIGPROF,
+    pub const PROF: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGPROF) });
     /// `SIGWINCH`
     #[cfg(not(target_os = "vita"))]
-    Winch = c::SIGWINCH,
+    pub const WINCH: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGWINCH) });
     /// `SIGIO`, aka `SIGPOLL`
     #[doc(alias = "Poll")]
     #[cfg(not(any(target_os = "haiku", target_os = "vita")))]
-    Io = c::SIGIO,
+    pub const IO: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGIO) });
     /// `SIGPWR`
     #[cfg(not(any(bsd, target_os = "haiku", target_os = "hurd", target_os = "vita")))]
     #[doc(alias = "Pwr")]
-    Power = c::SIGPWR,
+    pub const POWER: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGPWR) });
     /// `SIGSYS`, aka `SIGUNUSED`
     #[doc(alias = "Unused")]
-    Sys = c::SIGSYS,
+    pub const SYS: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGSYS) });
     /// `SIGEMT`
     #[cfg(any(
         bsd,
@@ -134,40 +152,109 @@ pub enum Signal {
             )
         )
     ))]
-    Emt = c::SIGEMT,
+    pub const EMT: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGEMT) });
     /// `SIGINFO`
     #[cfg(bsd)]
-    Info = c::SIGINFO,
+    pub const INFO: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGINFO) });
     /// `SIGTHR`
     #[cfg(target_os = "freebsd")]
     #[doc(alias = "Lwp")]
-    Thr = c::SIGTHR,
+    pub const THR: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGTHR) });
     /// `SIGLIBRT`
     #[cfg(target_os = "freebsd")]
-    Librt = c::SIGLIBRT,
+    pub const LIBRT: Self = Self(unsafe { NonZeroI32::new_unchecked(c::SIGLIBRT) });
 }
 
 impl Signal {
-    /// Convert a raw signal number into a `Signal`, if possible.
-    pub fn from_raw(sig: ffi::c_int) -> Option<Self> {
+    /// Convert a `Signal` to a raw signal number.
+    ///
+    /// To convert to a `NonZeroI32`, use [`Signal::as_raw_nonzero`].
+    #[inline]
+    pub const fn as_raw(self) -> i32 {
+        self.0.get()
+    }
+
+    /// Convert a `Signal` to a raw non-zero signal number.
+    #[inline]
+    pub const fn as_raw_nonzero(self) -> NonZeroI32 {
+        self.0
+    }
+
+    /// `SIGRTMIN + n`—Convert a “real-time” signal offset into a `Signal`.
+    ///
+    /// This function adds `n` to the libc `SIGRTMIN` value to construct the
+    /// raw signal value. If the result is greater than the platform `SIGRTMAX`
+    /// value, it returns `None`.
+    #[doc(alias = "SIGRTMIN", alias = "SIGRTMAX")]
+    #[cfg(feature = "use-libc-sigrt")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "use-libc-sigrt")))]
+    #[cfg(any(linux_like, solarish, target_os = "hurd"))]
+    pub fn rt(n: i32) -> Option<Self> {
+        let min = libc_sigrt_min();
+        let sig = min.wrapping_add(n);
+        if sig >= min && sig <= libc_sigrt_max() {
+            // SAFETY: Values at least `SIGRTMIN` will never be zero.
+            let sig = unsafe { NonZeroI32::new_unchecked(sig) };
+            Some(Self(sig))
+        } else {
+            None
+        }
+    }
+
+    /// `SIGRTMIN`—Return the minimum “real-time” signal value.
+    ///
+    /// Use [`Signal::rt`] to construct `SIGRTMIN + n` values.
+    #[doc(alias = "SIGRTMIN")]
+    #[cfg(feature = "use-libc-sigrt")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "use-libc-sigrt")))]
+    pub fn rt_min() -> Self {
+        // SAFETY: The libc is telling us this is the value it wants us to use.
+        unsafe { Self::from_raw_unchecked(libc_sigrt_min()) }
+    }
+
+    /// `SIGRTMAX`—Return the maximum “real-time” signal value.
+    #[doc(alias = "SIGRTMAX")]
+    #[cfg(feature = "use-libc-sigrt")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "use-libc-sigrt")))]
+    pub fn rt_max() -> Self {
+        // SAFETY: The libc is telling us this is the value it wants us to use.
+        unsafe { Self::from_raw_unchecked(libc_sigrt_max()) }
+    }
+
+    /// Convert a raw signal number into a `Signal`.
+    #[cfg(feature = "use-libc-sigrt")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "use-libc-sigrt")))]
+    pub fn from_raw(sig: i32) -> Option<Self> {
+        if let Some(non_zero) = NonZeroI32::new(sig) {
+            Self::from_raw_nonzero(non_zero)
+        } else {
+            None
+        }
+    }
+
+    /// Convert a raw non-zero signal number into a `Signal`.
+    #[cfg(feature = "use-libc-sigrt")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "use-libc-sigrt")))]
+    pub fn from_raw_nonzero(non_zero: NonZeroI32) -> Option<Self> {
+        let sig = non_zero.get();
         match sig {
-            c::SIGHUP => Some(Self::Hup),
-            c::SIGINT => Some(Self::Int),
-            c::SIGQUIT => Some(Self::Quit),
-            c::SIGILL => Some(Self::Ill),
-            c::SIGTRAP => Some(Self::Trap),
-            c::SIGABRT => Some(Self::Abort),
-            c::SIGBUS => Some(Self::Bus),
-            c::SIGFPE => Some(Self::Fpe),
-            c::SIGKILL => Some(Self::Kill),
+            c::SIGHUP => Some(Self::HUP),
+            c::SIGINT => Some(Self::INT),
+            c::SIGQUIT => Some(Self::QUIT),
+            c::SIGILL => Some(Self::ILL),
+            c::SIGTRAP => Some(Self::TRAP),
+            c::SIGABRT => Some(Self::ABORT),
+            c::SIGBUS => Some(Self::BUS),
+            c::SIGFPE => Some(Self::FPE),
+            c::SIGKILL => Some(Self::KILL),
             #[cfg(not(target_os = "vita"))]
-            c::SIGUSR1 => Some(Self::Usr1),
-            c::SIGSEGV => Some(Self::Segv),
+            c::SIGUSR1 => Some(Self::USR1),
+            c::SIGSEGV => Some(Self::SEGV),
             #[cfg(not(target_os = "vita"))]
-            c::SIGUSR2 => Some(Self::Usr2),
-            c::SIGPIPE => Some(Self::Pipe),
-            c::SIGALRM => Some(Self::Alarm),
-            c::SIGTERM => Some(Self::Term),
+            c::SIGUSR2 => Some(Self::USR2),
+            c::SIGPIPE => Some(Self::PIPE),
+            c::SIGALRM => Some(Self::ALARM),
+            c::SIGTERM => Some(Self::TERM),
             #[cfg(not(any(
                 bsd,
                 solarish,
@@ -188,36 +275,36 @@ impl Signal {
                     ),
                 )
             )))]
-            c::SIGSTKFLT => Some(Self::Stkflt),
+            c::SIGSTKFLT => Some(Self::STKFLT),
             #[cfg(not(target_os = "vita"))]
-            c::SIGCHLD => Some(Self::Child),
+            c::SIGCHLD => Some(Self::CHILD),
             #[cfg(not(target_os = "vita"))]
-            c::SIGCONT => Some(Self::Cont),
+            c::SIGCONT => Some(Self::CONT),
             #[cfg(not(target_os = "vita"))]
-            c::SIGSTOP => Some(Self::Stop),
+            c::SIGSTOP => Some(Self::STOP),
             #[cfg(not(target_os = "vita"))]
-            c::SIGTSTP => Some(Self::Tstp),
+            c::SIGTSTP => Some(Self::TSTP),
             #[cfg(not(target_os = "vita"))]
-            c::SIGTTIN => Some(Self::Ttin),
+            c::SIGTTIN => Some(Self::TTIN),
             #[cfg(not(target_os = "vita"))]
-            c::SIGTTOU => Some(Self::Ttou),
+            c::SIGTTOU => Some(Self::TTOU),
             #[cfg(not(target_os = "vita"))]
-            c::SIGURG => Some(Self::Urg),
+            c::SIGURG => Some(Self::URG),
             #[cfg(not(target_os = "vita"))]
-            c::SIGXCPU => Some(Self::Xcpu),
+            c::SIGXCPU => Some(Self::XCPU),
             #[cfg(not(target_os = "vita"))]
-            c::SIGXFSZ => Some(Self::Xfsz),
+            c::SIGXFSZ => Some(Self::XFSZ),
             #[cfg(not(target_os = "vita"))]
-            c::SIGVTALRM => Some(Self::Vtalarm),
+            c::SIGVTALRM => Some(Self::VTALARM),
             #[cfg(not(target_os = "vita"))]
-            c::SIGPROF => Some(Self::Prof),
+            c::SIGPROF => Some(Self::PROF),
             #[cfg(not(target_os = "vita"))]
-            c::SIGWINCH => Some(Self::Winch),
+            c::SIGWINCH => Some(Self::WINCH),
             #[cfg(not(any(target_os = "haiku", target_os = "vita")))]
-            c::SIGIO => Some(Self::Io),
+            c::SIGIO => Some(Self::IO),
             #[cfg(not(any(bsd, target_os = "haiku", target_os = "hurd", target_os = "vita")))]
-            c::SIGPWR => Some(Self::Power),
-            c::SIGSYS => Some(Self::Sys),
+            c::SIGPWR => Some(Self::POWER),
+            c::SIGSYS => Some(Self::SYS),
             #[cfg(any(
                 bsd,
                 solarish,
@@ -235,19 +322,141 @@ impl Signal {
                     )
                 )
             ))]
-            c::SIGEMT => Some(Self::Emt),
+            c::SIGEMT => Some(Self::EMT),
             #[cfg(bsd)]
-            c::SIGINFO => Some(Self::Info),
+            c::SIGINFO => Some(Self::INFO),
             #[cfg(target_os = "freebsd")]
-            c::SIGTHR => Some(Self::Thr),
+            c::SIGTHR => Some(Self::THR),
             #[cfg(target_os = "freebsd")]
-            c::SIGLIBRT => Some(Self::Librt),
-            _ => None,
+            c::SIGLIBRT => Some(Self::LIBRT),
+            _ => {
+                if sig >= libc_sigrt_min() && sig <= libc_sigrt_max() {
+                    Some(Self(non_zero))
+                } else {
+                    None
+                }
+            }
+        }
+    }
+
+    /// Convert a raw signal number into a `Signal` without checks.
+    ///
+    /// See [`Signal::from_raw`] to do this with checks.
+    ///
+    /// See [`Signal::from_raw_nonzero_unchecked`] if you already have a
+    /// `NonZeroI32`.
+    ///
+    /// # Safety
+    ///
+    /// `sig` must be a valid and non-zero signal number.
+    ///
+    /// And, if `sig` is a signal number reserved by the libc, such as a value
+    /// from [`SIGRTMIN`] to [`SIGRTMAX`] inclusive, then the resulting
+    /// `Signal` must not be used to send any signals.
+    ///
+    /// [`SIGRTMIN`]: https://docs.rs/libc/latest/libc/fn.SIGRTMIN.html
+    /// [`SIGRTMAX`]: https://docs.rs/libc/latest/libc/fn.SIGRTMAX.html
+    #[inline]
+    pub const unsafe fn from_raw_unchecked(sig: i32) -> Self {
+        Self::from_raw_nonzero_unchecked(NonZeroI32::new_unchecked(sig))
+    }
+
+    /// Convert a raw non-zero signal number into a `Signal` without checks.
+    ///
+    /// See [`Signal::from_raw`] to do this with checks.
+    ///
+    /// # Safety
+    ///
+    /// `sig` must be a valid signal number.
+    ///
+    /// And, if `sig` is a signal number reserved by the libc, such as a value
+    /// from [`SIGRTMIN`] to [`SIGRTMAX`] inclusive, then the resulting
+    /// `Signal` must not be used to send any signals.
+    ///
+    /// [`SIGRTMIN`]: https://docs.rs/libc/latest/libc/fn.SIGRTMIN.html
+    /// [`SIGRTMAX`]: https://docs.rs/libc/latest/libc/fn.SIGRTMAX.html
+    #[inline]
+    pub const unsafe fn from_raw_nonzero_unchecked(sig: NonZeroI32) -> Self {
+        Self(sig)
+    }
+}
+
+/// Return the libc `SIGRTMIN` value.
+#[cfg(any(linux_like, solarish, target_os = "hurd"))]
+#[cfg(feature = "use-libc-sigrt")]
+fn libc_sigrt_min() -> i32 {
+    // SAFETY: These are the ABI-compatible ways to obtain the `SIGRTMIN`
+    // value.
+    #[cfg(any(linux_like, target_os = "hurd"))]
+    unsafe {
+        extern "C" {
+            fn __libc_current_sigrtmin() -> crate::ffi::c_int;
         }
+        __libc_current_sigrtmin()
+    }
+    #[cfg(solarish)]
+    unsafe {
+        libc::SIGRTMIN()
     }
 }
 
-#[test]
-fn test_sizes() {
-    assert_eq_size!(Signal, c::c_int);
+/// Return the libc `SIGRTMAX` value.
+#[cfg(any(linux_like, solarish, target_os = "hurd"))]
+#[cfg(feature = "use-libc-sigrt")]
+fn libc_sigrt_max() -> i32 {
+    // SAFETY: These are the ABI-compatible ways to obtain the `SIGRTMAX`
+    // value.
+    #[cfg(any(linux_like, target_os = "hurd"))]
+    unsafe {
+        extern "C" {
+            fn __libc_current_sigrtmax() -> crate::ffi::c_int;
+        }
+        __libc_current_sigrtmax()
+    }
+    #[cfg(solarish)]
+    unsafe {
+        libc::SIGRTMAX()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_basics() {
+        assert_eq!(Signal::HUP.as_raw(), libc::SIGHUP);
+        unsafe {
+            assert_eq!(Signal::from_raw_unchecked(libc::SIGHUP), Signal::HUP);
+            assert_eq!(
+                Signal::from_raw_nonzero_unchecked(NonZeroI32::new(libc::SIGHUP).unwrap()),
+                Signal::HUP
+            );
+        }
+    }
+
+    #[cfg(any(linux_like, solarish, target_os = "hurd"))]
+    #[cfg(feature = "use-libc-sigrt")]
+    #[test]
+    fn test_sigrt() {
+        assert_eq!(libc::SIGRTMIN(), Signal::rt_min().as_raw());
+        assert_eq!(libc::SIGRTMIN(), Signal::rt_min().as_raw_nonzero().get());
+        assert_eq!(libc::SIGRTMAX(), Signal::rt_max().as_raw());
+        assert_eq!(libc::SIGRTMAX(), Signal::rt_max().as_raw_nonzero().get());
+        assert_eq!(Signal::rt(0).unwrap(), Signal::rt_min());
+        // POSIX guarantees at least 8 values.
+        assert_ne!(Signal::rt(7).unwrap(), Signal::rt_min());
+        assert_ne!(Signal::rt(7).unwrap(), Signal::rt_max());
+        assert_eq!(Signal::rt(7).unwrap().as_raw(), libc::SIGRTMIN() + 7);
+        assert_eq!(
+            Signal::rt(7).unwrap().as_raw_nonzero().get(),
+            libc::SIGRTMIN() + 7
+        );
+        assert!(Signal::from_raw(0).is_none());
+        for raw in libc::SIGRTMIN()..=libc::SIGRTMAX() {
+            assert!(Signal::from_raw(raw).is_some());
+        }
+        assert!(Signal::from_raw(libc::SIGRTMIN() - 1).is_none());
+        assert!(Signal::from_raw(libc::SIGRTMAX() + 1).is_none());
+    }
 }
diff --git a/src/system.rs b/src/system.rs
index ee3e7a087..8b75271bd 100644
--- a/src/system.rs
+++ b/src/system.rs
@@ -199,9 +199,9 @@ pub fn setdomainname(name: &[u8]) -> io::Result<()> {
 pub enum RebootCommand {
     /// Disables the Ctrl-Alt-Del keystroke.
     ///
-    /// When disabled, the keystroke will send a [`Signal::Int`] to pid 1.
+    /// When disabled, the keystroke will send a [`Signal::INT`] to pid 1.
     ///
-    /// [`Signal::Int`]: crate::process::Signal::Int
+    /// [`Signal::INT`]: crate::process::Signal::INT
     CadOff = c::LINUX_REBOOT_CMD_CAD_OFF,
     /// Enables the Ctrl-Alt-Del keystroke.
     ///


Test patch: