clock_bound_d/
signal.rs

1//! Unix signal handler registration.
2//!
3//! Use the nix crate to register signal callbacks, while keeping any specific notion of libc
4//! within this module only. The callbacks are registered into a HashMap and looked up when a
5//! signal is received.
6
7use lazy_static::lazy_static;
8use libc;
9use nix::sys::signal;
10use std::collections::HashMap;
11use std::io::Result;
12use std::sync::Mutex;
13use tracing::{error, info};
14
15/// Defines the types of callback that can be registered with the signal handler
16type Callback = fn() -> ();
17
18/// Tiny structure to maintain the association of callbacks registered with signals.
19///
20/// The internal representation is a hashmap of signal number and callbacks.
21struct SignalHandler {
22    handlers: HashMap<signal::Signal, Callback>,
23}
24
25impl SignalHandler {
26    /// A new empty SignalHandler structure.
27    fn new() -> SignalHandler {
28        SignalHandler {
29            handlers: HashMap::new(),
30        }
31    }
32
33    /// Get the callback associated with a signal number.
34    ///
35    /// Returns the callback wrapped in an Option. Returns None if no callback has been registered
36    /// with the given signal.
37    fn get_callback(&self, sig: signal::Signal) -> Option<&Callback> {
38        self.handlers.get(&sig)
39    }
40
41    /// Set / Overwrite callback for a given signal
42    ///
43    /// Silently ignore the return value of inserting a new callback over an existing one in the
44    /// HashMap. Last callback registered wins.
45    fn add_callback(&mut self, sig: signal::Signal, callback: Callback) {
46        self.handlers.insert(sig, callback);
47    }
48}
49
50lazy_static! {
51    /// Global SignalHandler structure, instantiated on first access.
52    ///
53    /// Signal handlers have a predefined signature, easier to provide a static variable to lookup the
54    /// callbacks to run.
55    static ref SIGNAL_HANDLERS: Mutex<SignalHandler> = Mutex::new(SignalHandler::new());
56}
57
58/// Main signal handler function.
59///
60/// This function is the one and unique signal handler, looking up and running registered callbacks.
61/// This level of indirection helps hide libc specific details away. Potential drawback is that
62/// assessing complexity of the callabck is less obvious.
63extern "C" fn main_signal_handler(signum: libc::c_int) {
64    // Although unlikely, there is always the risk the registration function holds the lock while
65    // the main thread is interrupted by a signal. Do not want to deadlock in interrupted context.
66    // Try the lock, and bail out if it cannot be acquired.
67    let handlers = match SIGNAL_HANDLERS.try_lock() {
68        Ok(handlers) => handlers,
69        Err(_) => return, // TODO: log an error?
70    };
71
72    if let Ok(sig) = signal::Signal::try_from(signum) {
73        if let Some(cb) = handlers.get_callback(sig) {
74            cb()
75        }
76    }
77}
78
79/// Enable UNIX signal via sigaction.
80///
81/// Gathers all libc crate and C types unsafe code here.
82fn enable_signal(sig: signal::Signal) -> Result<()> {
83    // Always register the main signal handler
84    let handler = signal::SigHandler::Handler(main_signal_handler);
85    let mask = signal::SigSet::empty();
86    let mut flags = signal::SaFlags::empty();
87    flags.insert(signal::SaFlags::SA_RESTART);
88    flags.insert(signal::SaFlags::SA_SIGINFO);
89    flags.insert(signal::SaFlags::SA_NOCLDSTOP);
90
91    let sig_action = signal::SigAction::new(handler, flags, mask);
92
93    let result = unsafe { signal::sigaction(sig, &sig_action) };
94
95    match result {
96        Ok(_) => Ok(()),
97        Err(_) => Err(std::io::Error::last_os_error()),
98    }
99}
100
101/// Enable signal and register associated callback.
102///
103/// Signal handling is done through indirection, hidden from the caller. The master signal handler
104/// is always registered to handle the signal. It is then charged with looking up and running the
105/// callback provided.
106///
107/// Should be called on the main thread.
108///
109/// # Examples
110///
111/// ```rust
112/// use nix::sys::signal;
113/// use clock_bound_d::signal::register_signal_callback;
114///
115/// fn on_sighup() {
116///   println!("Got HUP'ed!!");
117/// }
118///
119/// register_signal_callback(signal::SIGHUP, on_sighup);
120///
121/// ```
122pub fn register_signal_callback(sig: signal::Signal, callback: Callback) -> Result<()> {
123    // All signals are managed and handled on the main thread. It is safe to lock the mutex and
124    // block until acquired. The signal handler may hold the Mutex lock, but releases it once
125    // signal handling and main execution resumes.
126    let mut handlers = SIGNAL_HANDLERS.lock().unwrap();
127    handlers.add_callback(sig, callback);
128
129    // The new callback is registered, the signal can be handled
130    match enable_signal(sig) {
131        Ok(_) => {
132            info!("Registered callback for signal {}", sig);
133            Ok(())
134        }
135        Err(e) => {
136            error!("Failed to register callback for signal {}: {}", sig, e);
137            Err(e)
138        }
139    }
140}
141
142#[cfg(test)]
143mod t_signal {
144
145    use super::*;
146
147    /// Assert that a callaback can be registered and retrieved with the same signal.
148    #[test]
149    fn test_add_and_get_callback() {
150        // Testing side effects is inherently unsafe
151        static mut VAL: i32 = 0;
152        unsafe {
153            let mut handlers = SignalHandler::new();
154            VAL = 2;
155            fn do_double() {
156                unsafe { VAL *= 2 }
157            }
158            handlers.add_callback(signal::SIGHUP, do_double);
159            let cb = handlers.get_callback(signal::SIGHUP).unwrap();
160            cb();
161            assert_eq!(4, VAL);
162        }
163    }
164
165    /// Assert that the last callback registered is retrieved and triggered upon multiple
166    /// registrations.
167    #[test]
168    fn test_last_callback_wins() {
169        // Testing side effects is inherently unsafe
170        static mut VAL: i32 = 2;
171        unsafe {
172            let mut handlers = SignalHandler::new();
173            //VAL = 2;
174            fn do_double() {
175                unsafe { VAL *= 2 }
176            }
177            fn do_triple() {
178                unsafe { VAL *= 3 }
179            }
180            fn do_quadruple() {
181                unsafe { VAL *= 4 }
182            }
183            handlers.add_callback(signal::SIGHUP, do_double);
184            handlers.add_callback(signal::SIGHUP, do_triple);
185            handlers.add_callback(signal::SIGHUP, do_quadruple);
186            let cb = handlers.get_callback(signal::SIGHUP).unwrap();
187            cb();
188            assert_eq!(8, VAL);
189        }
190    }
191
192    /// Assert that None is returned if no callback is registered for the signal.
193    #[test]
194    fn test_get_none_on_missing_callbacks() {
195        let mut handlers = SignalHandler::new();
196        fn do_nothing() {}
197        handlers.add_callback(signal::SIGHUP, do_nothing);
198        let cb = handlers.get_callback(signal::SIGINT);
199        assert_eq!(None, cb);
200    }
201}