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) }
}