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}