devela/sys/os/linux/fns/
signal.rs

1// devela::sys::os::linux::fns::signal
2//
3//! signal related functions
4//
5
6use crate::{
7    linux_sys_rt_sigaction, AtomicOrdering, AtomicPtr, LinuxSigaction, LinuxSigset,
8    LINUX_SIGACTION as SA,
9};
10use core::{mem::transmute, ptr::null_mut};
11
12/// Registers multiple signals using a handler function that never returns.
13///
14/// # Examples
15/// ```no_run
16/// use std::{process::exit, time::Duration, thread::sleep};
17/// use devela::{linux_sig_handler_no_return, LINUX_SIGNAL as LS};
18///
19/// fn handler(sig: i32) -> ! {
20///    println!("\nsignal `{sig}` received! exiting. . .");
21///    exit(1);
22/// }
23///
24/// fn main() {
25///     // handle all the signals used to quit
26///     linux_sig_handler_no_return(handler, &[LS::SIGINT, LS::SIGQUIT, LS::SIGSEGV, LS::SIGABRT]);
27///     // press Ctrl+C before the time expires to catch the SIGINT signal
28///     sleep(Duration::from_secs(2));
29///     println!("bye");
30/// }
31/// ```
32///
33/// # Rationale
34/// It would be very nice to be able to register a signal handler that can return,
35/// unfortunately I've been unable to make it work.
36///
37/// Apparently the handler needs the [`SA_RESTORER`] flag to run, but doing so
38/// without providing a restorer function produces a segmentation fault. The only
39/// way to simply avoid that is to not return from the handler function.
40///
41/// The `libc` library sets it up correctly but doing so manually seems a too
42/// complex too low level task.
43///
44/// [`SA_RESTORER`]: SA::SA_RESTORER
45pub fn linux_sig_handler_no_return(handler: fn(i32) -> !, signals: &[i32]) {
46    // We store the given `handler` function in a static to be able to call it
47    // from the new extern function which can't capture its environment.
48    static HANDLER: AtomicPtr<fn(i32) -> !> = AtomicPtr::new(null_mut());
49    HANDLER.store(handler as *mut _, AtomicOrdering::SeqCst);
50
51    extern "C" fn c_handler(sig: i32) {
52        let handler = HANDLER.load(AtomicOrdering::SeqCst);
53        if !handler.is_null() {
54            #[allow(clippy::crosspointer_transmute)]
55            // SAFETY: The non-null pointer is originally created from a `fn(i32) -> !` pointer.
56            let handler = unsafe { transmute::<*mut fn(i32) -> !, fn(i32) -> !>(handler) };
57            handler(sig);
58        }
59    }
60
61    // Apparently Rust doesn't call the handler unless we set the SA_RESTORER flag.
62    let flags = SA::SA_RESETHAND | SA::SA_RESTORER;
63    let mask = LinuxSigset::default();
64    let sigaction = LinuxSigaction::new(c_handler, flags, mask);
65
66    for s in signals {
67        // make sure the signal is a valid number
68        if (1..=36).contains(s) {
69            unsafe {
70                let _ = linux_sys_rt_sigaction(*s, &sigaction, null_mut(), LinuxSigset::size());
71            }
72        }
73    }
74}