| //! 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))] |
| 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) } |
| } |