1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
//! Cleaning up signals. //! //! The routines in this module allow resetting the signals of an application back to defaults. //! This is intended for the following situation: //! //! * A terminal signal (eg. a `SIGTERM`, `SIGINT` or something similar) is received. //! * The application resets the signal handlers to defaults. //! * The application proceeds to perform some kind of shutdown (saving data, cleaning up, ...). //! * If another such signal is received, the application is terminated right away the hard way, //! without finishing the shutdown. //! //! The alternative of leaving the original signals in place might be problematic in case the //! shutdown takes a long time or when it gets stuck. In such case the application would appear to //! ignore the signal and just refuse to die. //! //! There are two ways to perform the reset: //! * Registering the reset as part of the signal handlers. This is more reliable (even in case the //! application is already stuck in some kind of infinite loop, it would still work). This is //! done by [register]. //! * Manually resetting the handlers just before the shutdown. This is done with [cleanup_signal]. use std::io::Error; #[cfg(not(windows))] use std::ptr; use libc::{c_int, sighandler_t, SIG_ERR}; #[cfg(not(windows))] use libc::SIG_DFL; // Unfortunately, not exported on windows :-(. Checked this actually works by tests/default.rs. #[cfg(windows)] const SIG_DFL: sighandler_t = 0; pub use signal_hook_registry::unregister_signal; use crate::SigId; /// Resets the signal handler to the default one. /// /// This is the lowest level wrapper around resetting the signal handler to the OS default. It /// doesn't remove the hooks (though they will not get called), it doesn't handle errors and it /// doesn't return any previous chained signals. The hooks will simply stay registered but dormant. /// /// This function is async-signal-safe. However, you might prefer to use either [cleanup_signal] or /// [register]. /// /// # Warning /// /// This action is irreversible, once called, registering more hooks for the same signal will have /// no effect (neither the old nor the new ones will be active but the registration will appear to /// have succeeded). /// /// This behaviour **can change** in future versions without considering it a breaking change. /// /// In other words, this is expected to be called only before terminating the application and no /// further manipulation of the given signal is supported in any way. While it won't cause UB, it /// *will* produce unexpected results. pub fn cleanup_raw(signal: c_int) -> sighandler_t { unsafe { ::libc::signal(signal, SIG_DFL) } } /// Resets the signal handler to the default one and removes all its hooks. /// /// This resets the signal to the OS default. It doesn't revert to calling any previous signal /// handlers (the ones not handled by `signal-hook`). All the hooks registered for this signal are /// removed. /// /// The intended use case is making sure further instances of a terminal signal have immediate /// effect. If eg. a CTRL+C is pressed, the application removes all signal handling and proceeds to /// its own shutdown phase. If the shutdown phase takes too long or gets stuck, the user may press /// CTRL+C again which will then kill the application immediately, by a default signal action. /// /// # Warning /// /// This action is *global* (affecting hooks some other library or unrelated part of program /// registered) and *irreversible*. Once called, registering new hooks for this signal has no /// further effect (they'll appear to be registered, but they won't be called by the signal). The /// latter may change in the future and it won't be considered a breaking change. /// /// In other words, this is expected to be called only once the application enters its terminal /// state and is not supported otherwise. /// /// The function is **not** async-signal-safe. See [register] and [cleanup_raw] if you intend to /// reset the signal directly from inside the signal handler itself. /// /// # Examples /// /// ```rust /// # extern crate libc; /// # extern crate signal_hook; /// # /// # use std::io::Error; /// # use std::sync::atomic::{AtomicBool, Ordering}; /// # use std::sync::Arc; /// # /// # fn keep_processing() { std::thread::sleep(std::time::Duration::from_millis(50)); } /// # fn app_cleanup() {} /// use signal_hook::{cleanup, flag, SIGTERM}; /// /// fn main() -> Result<(), Error> { /// let terminated = Arc::new(AtomicBool::new(false)); /// flag::register(SIGTERM, Arc::clone(&terminated))?; /// # unsafe { libc::raise(SIGTERM) }; /// /// while !terminated.load(Ordering::Relaxed) { /// keep_processing(); /// } /// /// cleanup::cleanup_signal(SIGTERM)?; /// app_cleanup(); /// Ok(()) /// } /// ``` pub fn cleanup_signal(signal: c_int) -> Result<(), Error> { // We use `signal` both on unix and windows here. Unlike with regular functions, usage of // SIG_DFL is portable and much more convenient to use. let result = cleanup_raw(signal); // The cast is needed on windows :-|. if result == SIG_ERR as _ { return Err(Error::last_os_error()); } unregister_signal(signal); Ok(()) } #[cfg(not(windows))] #[allow(clippy::map_collect_result_unit)] // try_for_each is too new fn verify_signals_exist(signals: &[c_int]) -> Result<(), Error> { signals .iter() .map(|s| -> Result<(), Error> { if unsafe { ::libc::sigaction(*s, ptr::null(), ptr::null_mut()) } == -1 { Err(Error::last_os_error()) } else { Ok(()) } }) .collect() } #[cfg(windows)] fn verify_signals_exist(_: &[c_int]) -> Result<(), Error> { // TODO: Do we have a way to check if the signals are valid on windows too? Ok(()) } /// Register a cleanup after receiving a signal. /// /// Registers an action that, after receiving `signal`, will reset all signals specified in /// `cleanup` to their OS defaults. The reset is done as part of the signal handler. /// /// The intended use case is that at CTRL+C (or something similar), the application starts shutting /// down. This might take some time so by resetting all terminal signals to the defaults at that /// time makes sure a second CTRL+C results in immediate (hard) termination of the application. /// /// The hooks are still left inside and any following hooks after the reset are still run. Only the /// next signal will be affected (and the hooks will be inert). /// /// # Warning /// /// The reset as part of the action is *global* and *irreversible*. All signal hooks and all /// signals registered outside of `signal-hook` are affected and won't be run any more. Registering /// more hooks for the same signals as cleaned will have no effect. /// /// The latter part of having no effect may be changed in the future, do not rely on it. /// Preferably, don't manipulate the signal any longer. /// /// # Examples /// /// ```rust /// # extern crate libc; /// # extern crate signal_hook; /// # /// # use std::io::Error; /// # use std::sync::atomic::{AtomicBool, Ordering}; /// # use std::sync::Arc; /// # /// # fn keep_processing() { std::thread::sleep(std::time::Duration::from_millis(50)); } /// # fn app_cleanup() {} /// use signal_hook::{cleanup, flag, SIGINT, SIGTERM}; /// /// fn main() -> Result<(), Error> { /// let terminated = Arc::new(AtomicBool::new(false)); /// flag::register(SIGTERM, Arc::clone(&terminated))?; /// cleanup::register(SIGTERM, vec![SIGTERM, SIGINT])?; /// # unsafe { libc::raise(SIGTERM) }; /// /// while !terminated.load(Ordering::Relaxed) { /// keep_processing(); /// } /// /// app_cleanup(); /// Ok(()) /// } /// ``` pub fn register(signal: c_int, cleanup: Vec<c_int>) -> Result<SigId, Error> { verify_signals_exist(&cleanup)?; let hook = move || { for sig in &cleanup { // Note: we are ignoring the errors here. We have no way to handle them and the only // possible ones are invalid signals ‒ which we should have handled by // verify_signals_exist above. cleanup_raw(*sig); } }; unsafe { crate::register(signal, hook) } }