Skip to content

Commit eb79087

Browse files
committed
Emulating default actions
1 parent c1ee18d commit eb79087

File tree

6 files changed

+219
-47
lines changed

6 files changed

+219
-47
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
* `low_level::emulate_default_handler` to emulate whatever default handler would
2+
do.
13
* `low_level::signal_name` to look up human readable name.
24
* The `Origin`'s debug output now contains the human readable name of the
35
signal.

examples/print.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use libc::c_int;
2+
use signal_hook::consts::signal::*;
3+
use signal_hook::low_level;
4+
5+
use std::io::Error;
6+
7+
#[cfg(feature = "extended-siginfo")]
8+
type Signals =
9+
signal_hook::iterator::SignalsInfo<signal_hook::iterator::exfiltrator::origin::WithOrigin>;
10+
11+
#[cfg(not(feature = "extended-siginfo"))]
12+
use signal_hook::iterator::Signals;
13+
14+
fn main() -> Result<(), Error> {
15+
const SIGNALS: &[c_int] = &[
16+
SIGTERM, SIGQUIT, SIGINT, SIGTSTP, SIGWINCH, SIGHUP, SIGCHLD, SIGCONT,
17+
];
18+
let mut sigs = Signals::new(SIGNALS)?;
19+
for signal in &mut sigs {
20+
eprintln!("Received signal {:?}", signal);
21+
#[cfg(feature = "extended-siginfo")]
22+
let signal = signal.signal;
23+
// After printing it, do whatever the signal was supposed to do in the first place
24+
low_level::emulate_default_handler(signal)?;
25+
}
26+
Ok(())
27+
}

src/flag.rs

+46-6
Original file line numberDiff line numberDiff line change
@@ -141,22 +141,26 @@ use std::io::Error;
141141
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
142142
use std::sync::Arc;
143143

144-
use libc::c_int;
144+
use libc::{c_int, EINVAL};
145145

146-
use crate::SigId;
146+
use crate::{low_level, SigId};
147147

148148
/// Registers an action to set the flag to `true` whenever the given signal arrives.
149+
///
150+
/// # Panics
151+
///
152+
/// If the signal is one of the forbidden.
149153
pub fn register(signal: c_int, flag: Arc<AtomicBool>) -> Result<SigId, Error> {
150154
// We use SeqCst for two reasons:
151155
// * Signals should not come very often, so the performance does not really matter.
152156
// * We promise the order of actions, but setting different atomics with Relaxed or similar
153157
// would not guarantee the effective order.
154-
unsafe { crate::low_level::register(signal, move || flag.store(true, Ordering::SeqCst)) }
158+
unsafe { low_level::register(signal, move || flag.store(true, Ordering::SeqCst)) }
155159
}
156160

157161
/// Registers an action to set the flag to the given value whenever the signal arrives.
158162
pub fn register_usize(signal: c_int, flag: Arc<AtomicUsize>, value: usize) -> Result<SigId, Error> {
159-
unsafe { crate::low_level::register(signal, move || flag.store(value, Ordering::SeqCst)) }
163+
unsafe { low_level::register(signal, move || flag.store(value, Ordering::SeqCst)) }
160164
}
161165

162166
/// Terminate the application on a signal if the given condition is true.
@@ -175,17 +179,53 @@ pub fn register_usize(signal: c_int, flag: Arc<AtomicUsize>, value: usize) -> Re
175179
/// shutdown on the second run. Note that it matters in which order the actions are registered (the
176180
/// shutdown must go first). And yes, this also allows asking the user „Do you want to terminate“
177181
/// and disarming the abrupt shutdown if the user answers „No“.
182+
///
183+
/// # Panics
184+
///
185+
/// If the signal is one of the forbidden.
178186
pub fn register_conditional_shutdown(
179187
signal: c_int,
180188
status: c_int,
181189
condition: Arc<AtomicBool>,
182190
) -> Result<SigId, Error> {
183191
let action = move || {
184192
if condition.load(Ordering::SeqCst) {
185-
crate::low_level::exit(status);
193+
low_level::exit(status);
194+
}
195+
};
196+
unsafe { low_level::register(signal, action) }
197+
}
198+
199+
/// Conditionally runs an emulation of the default action on the given signal.
200+
///
201+
/// If the provided condition is true at the time of invoking the signal handler, the equivalent of
202+
/// the default action of the given signal is run. It is a bit similar to
203+
/// [`register_conditional_shutdown`], except that it doesn't terminate for non-termination
204+
/// signals, it runs their default handler.
205+
///
206+
/// # Panics
207+
///
208+
/// If the signal is one of the forbidden
209+
///
210+
/// # Errors
211+
///
212+
/// Similarly to the [`emulate_default_handler`][low_level::emulate_default_handler] function, this
213+
/// one looks the signal up in a table. If it is unknown, an error is returned.
214+
///
215+
/// Additionally to that, any errors that can be caused by a registration of a handler can happen
216+
/// too.
217+
pub fn register_conditional_default(
218+
signal: c_int,
219+
condition: Arc<AtomicBool>,
220+
) -> Result<SigId, Error> {
221+
// Verify we know about this particular signal.
222+
low_level::signal_name(signal).ok_or_else(|| Error::from_raw_os_error(EINVAL))?;
223+
let action = move || {
224+
if condition.load(Ordering::SeqCst) {
225+
let _ = low_level::emulate_default_handler(signal);
186226
}
187227
};
188-
unsafe { crate::low_level::register(signal, action) }
228+
unsafe { low_level::register(signal, action) }
189229
}
190230

191231
#[cfg(test)]

src/lib.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@
103103
//! does not restore the default handler (such behaviour would be at times inconsistent with
104104
//! making the actions independent and there's no reasonable way to do so in a race-free way in a
105105
//! multi-threaded program while also dealing with signal handlers registered with other
106-
//! libraries). It is, however, possible to *emulate* the default handler ‒ there are only 4
106+
//! libraries). It is, however, possible to *emulate* the default handler (see the
107+
//! [`emulate_default_handler`][low_level::emulate_default_handler]) ‒ there are only 4
107108
//! default handlers:
108109
//! - Ignore. This is easy to emulate.
109110
//! - Abort. Depending on if you call it from within a signal handler of from outside, the
@@ -269,8 +270,8 @@
269270
//! if has_terminal {
270271
//! app.restore_term();
271272
//! has_terminal = false;
272-
//! // And actually stop ourselves, by a little trick.
273-
//! low_level::raise(SIGSTOP)?;
273+
//! // And actually stop ourselves.
274+
//! low_level::emulate_default_handler(SIGTSTP)?;
274275
//! }
275276
//! }
276277
//! SIGCONT => {

src/low_level/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mod signal_details;
1616

1717
pub use signal_hook_registry::{register, unregister};
1818

19-
pub use self::signal_details::signal_name;
19+
pub use self::signal_details::{emulate_default_handler, signal_name};
2020

2121
/// The usual raise, just the safe wrapper around it.
2222
///

src/low_level/signal_details.rs

+139-37
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,82 @@
11
//! Providing auxiliary information for signals.
22
3-
use libc::c_int;
3+
use std::io::Error;
4+
use std::mem;
5+
use std::ptr;
6+
7+
use libc::{c_int, EINVAL};
48

59
use crate::consts::signal::*;
10+
use crate::low_level;
11+
12+
#[derive(Clone, Copy, Debug)]
13+
enum DefaultKind {
14+
Ignore,
15+
#[cfg(not(windows))]
16+
Stop,
17+
Term,
18+
}
619

720
struct Details {
821
signal: c_int,
922
name: &'static str,
23+
default_kind: DefaultKind,
1024
}
1125

1226
macro_rules! s {
13-
($name: expr) => {
27+
($name: expr, $kind: ident) => {
1428
Details {
1529
signal: $name,
1630
name: stringify!($name),
31+
default_kind: DefaultKind::$kind,
1732
}
1833
};
1934
}
2035

2136
#[cfg(not(windows))]
2237
const DETAILS: &[Details] = &[
23-
s!(SIGABRT),
24-
s!(SIGALRM),
25-
s!(SIGBUS),
26-
s!(SIGCHLD),
27-
s!(SIGCONT),
28-
s!(SIGFPE),
29-
s!(SIGHUP),
30-
s!(SIGILL),
31-
s!(SIGINT),
32-
s!(SIGIO),
33-
s!(SIGKILL),
34-
s!(SIGPIPE),
35-
s!(SIGPROF),
36-
s!(SIGQUIT),
37-
s!(SIGSEGV),
38-
s!(SIGSTOP),
39-
s!(SIGSYS),
40-
s!(SIGTERM),
41-
s!(SIGTRAP),
42-
s!(SIGTSTP),
43-
s!(SIGTTIN),
44-
s!(SIGTTOU),
45-
s!(SIGURG),
46-
s!(SIGUSR1),
47-
s!(SIGUSR2),
48-
s!(SIGVTALRM),
49-
s!(SIGWINCH),
50-
s!(SIGXCPU),
51-
s!(SIGXFSZ),
38+
s!(SIGABRT, Term),
39+
s!(SIGALRM, Term),
40+
s!(SIGBUS, Term),
41+
s!(SIGCHLD, Ignore),
42+
// Technically, continue the process... but this is not done *by* the process.
43+
s!(SIGCONT, Ignore),
44+
s!(SIGFPE, Term),
45+
s!(SIGHUP, Term),
46+
s!(SIGILL, Term),
47+
s!(SIGINT, Term),
48+
s!(SIGIO, Ignore),
49+
// Can't override anyway, but...
50+
s!(SIGKILL, Term),
51+
s!(SIGPIPE, Term),
52+
s!(SIGPROF, Term),
53+
s!(SIGQUIT, Term),
54+
s!(SIGSEGV, Term),
55+
// Can't override anyway, but...
56+
s!(SIGSTOP, Stop),
57+
s!(SIGSYS, Term),
58+
s!(SIGTERM, Term),
59+
s!(SIGTRAP, Term),
60+
s!(SIGTSTP, Stop),
61+
s!(SIGTTIN, Stop),
62+
s!(SIGTTOU, Stop),
63+
s!(SIGURG, Ignore),
64+
s!(SIGUSR1, Term),
65+
s!(SIGUSR2, Term),
66+
s!(SIGVTALRM, Term),
67+
s!(SIGWINCH, Ignore),
68+
s!(SIGXCPU, Term),
69+
s!(SIGXFSZ, Term),
5270
];
5371

5472
#[cfg(windows)]
5573
const DETAILS: &[Details] = &[
56-
s!(SIGABRT),
57-
s!(SIGFPE),
58-
s!(SIGILL),
59-
s!(SIGINT),
60-
s!(SIGSEGV),
61-
s!(SIGTERM),
74+
s!(SIGABRT, Term),
75+
s!(SIGFPE, Term),
76+
s!(SIGILL, Term),
77+
s!(SIGINT, Term),
78+
s!(SIGSEGV, Term),
79+
s!(SIGTERM, Term),
6280
];
6381

6482
/// Provides a human-readable name of a signal.
@@ -77,6 +95,90 @@ pub fn signal_name(signal: c_int) -> Option<&'static str> {
7795
DETAILS.iter().find(|d| d.signal == signal).map(|d| d.name)
7896
}
7997

98+
#[cfg(not(windows))]
99+
fn restore_default(signal: c_int) -> Result<(), Error> {
100+
unsafe {
101+
// A C structure, supposed to be memset to 0 before use.
102+
let mut action: libc::sigaction = mem::zeroed();
103+
action.sa_sigaction = libc::SIG_DFL as _;
104+
if libc::sigaction(signal, &action, ptr::null_mut()) == 0 {
105+
Ok(())
106+
} else {
107+
Err(Error::last_os_error())
108+
}
109+
}
110+
}
111+
112+
#[cfg(windows)]
113+
fn restore_default(signal: c_int) -> Result<(), Error> {
114+
unsafe {
115+
// SIG_DFL = 0, but not in libc :-(
116+
if libc::signal(signal, 0) == 0 {
117+
Ok(())
118+
} else {
119+
Err(Error::last_os_error())
120+
}
121+
}
122+
}
123+
124+
/// Emulates the behaviour of a default handler for the provided signal.
125+
///
126+
/// This function does its best to provide the same action as the default handler would do, without
127+
/// disrupting the rest of the handling of such signal in the application. It is also
128+
/// async-signal-safe.
129+
///
130+
/// This function necessarily looks up the appropriate action in a table. That means it is possible
131+
/// your system has a signal that is not known to this function. In such case an error is returned
132+
/// (equivalent of `EINVAL`).
133+
///
134+
/// See also the [`register_conditional_default`][crate::flag::register_conditional_default].
135+
///
136+
/// # Warning
137+
///
138+
/// There's a short race condition in case of signals that terminate (either with or without a core
139+
/// dump). The emulation first resets the signal handler back to default (as the application is
140+
/// going to end, it's not a problem) and invokes it. But if some other thread installs a signal
141+
/// handler in the meantime (without assistance from `signal-hook`), it can happen this will be
142+
/// invoked by the re-raised signal.
143+
///
144+
/// This function will still terminate the application (there's a fallback on `abort`), the risk is
145+
/// invoking the newly installed signal handler. Note that manipulating the low-level signals is
146+
/// always racy in a multi-threaded program, therefore the described situation is already
147+
/// discouraged.
148+
///
149+
/// If you are uneasy about such race condition, the recommendation is to run relevant termination
150+
/// routine manually ([`exit`][super::exit] or [`abort`][super::abort]); they always do what they
151+
/// say, but slightly differ in externally observable behaviour from termination by a signal (the
152+
/// exit code will specify that the application exited, not that it terminated with a signal in the
153+
/// first case, and `abort` terminates on `SIGABRT`, so the detected termination signal may be
154+
/// different).
155+
pub fn emulate_default_handler(signal: c_int) -> Result<(), Error> {
156+
#[cfg(not(windows))]
157+
{
158+
if signal == SIGSTOP || signal == SIGKILL {
159+
return low_level::raise(signal);
160+
}
161+
}
162+
let kind = DETAILS
163+
.iter()
164+
.find(|d| d.signal == signal)
165+
.map(|d| d.default_kind)
166+
.ok_or_else(|| Error::from_raw_os_error(EINVAL))?;
167+
match kind {
168+
DefaultKind::Ignore => Ok(()),
169+
#[cfg(not(windows))]
170+
DefaultKind::Stop => low_level::raise(SIGSTOP),
171+
DefaultKind::Term => {
172+
if let Ok(()) = restore_default(signal) {
173+
let _ = low_level::raise(signal);
174+
}
175+
// Fallback if anything failed or someone managed to put some other action in in
176+
// between.
177+
unsafe { libc::abort() }
178+
}
179+
}
180+
}
181+
80182
#[cfg(test)]
81183
mod test {
82184
use super::*;

0 commit comments

Comments
 (0)