signal_hook/low_level/
signal_details.rs

1//! Providing auxiliary information for signals.
2
3use std::io::Error;
4use std::mem;
5use std::ptr;
6
7use libc::{c_int, EINVAL};
8#[cfg(not(windows))]
9use libc::{sigset_t, SIG_UNBLOCK};
10
11use crate::consts::signal::*;
12use crate::low_level;
13
14#[derive(Clone, Copy, Debug)]
15enum DefaultKind {
16    Ignore,
17    #[cfg(not(windows))]
18    Stop,
19    Term,
20}
21
22struct Details {
23    signal: c_int,
24    name: &'static str,
25    default_kind: DefaultKind,
26}
27
28macro_rules! s {
29    ($name: expr, $kind: ident) => {
30        Details {
31            signal: $name,
32            name: stringify!($name),
33            default_kind: DefaultKind::$kind,
34        }
35    };
36}
37
38#[cfg(not(windows))]
39const DETAILS: &[Details] = &[
40    s!(SIGABRT, Term),
41    s!(SIGALRM, Term),
42    s!(SIGBUS, Term),
43    s!(SIGCHLD, Ignore),
44    // Technically, continue the process... but this is not done *by* the process.
45    s!(SIGCONT, Ignore),
46    s!(SIGFPE, Term),
47    s!(SIGHUP, Term),
48    s!(SIGILL, Term),
49    s!(SIGINT, Term),
50    #[cfg(any(
51        target_os = "freebsd",
52        target_os = "dragonfly",
53        target_os = "netbsd",
54        target_os = "openbsd",
55        target_os = "macos"
56    ))]
57    s!(SIGINFO, Ignore),
58    #[cfg(not(target_os = "haiku"))]
59    s!(SIGIO, Ignore),
60    // Can't override anyway, but...
61    s!(SIGKILL, Term),
62    s!(SIGPIPE, Term),
63    s!(SIGPROF, Term),
64    s!(SIGQUIT, Term),
65    s!(SIGSEGV, Term),
66    // Can't override anyway, but...
67    s!(SIGSTOP, Stop),
68    s!(SIGSYS, Term),
69    s!(SIGTERM, Term),
70    s!(SIGTRAP, Term),
71    s!(SIGTSTP, Stop),
72    s!(SIGTTIN, Stop),
73    s!(SIGTTOU, Stop),
74    s!(SIGURG, Ignore),
75    s!(SIGUSR1, Term),
76    s!(SIGUSR2, Term),
77    s!(SIGVTALRM, Term),
78    s!(SIGWINCH, Ignore),
79    s!(SIGXCPU, Term),
80    s!(SIGXFSZ, Term),
81];
82
83#[cfg(windows)]
84const DETAILS: &[Details] = &[
85    s!(SIGABRT, Term),
86    s!(SIGFPE, Term),
87    s!(SIGILL, Term),
88    s!(SIGINT, Term),
89    s!(SIGSEGV, Term),
90    s!(SIGTERM, Term),
91];
92
93/// Provides a human-readable name of a signal.
94///
95/// Note that the name does not have to be known (in case it is some less common, or non-standard
96/// signal).
97///
98/// # Examples
99///
100/// ```
101/// # use signal_hook::low_level::signal_name;
102/// assert_eq!("SIGKILL", signal_name(9).unwrap());
103/// assert!(signal_name(142).is_none());
104/// ```
105pub fn signal_name(signal: c_int) -> Option<&'static str> {
106    DETAILS.iter().find(|d| d.signal == signal).map(|d| d.name)
107}
108
109#[cfg(not(windows))]
110fn restore_default(signal: c_int) -> Result<(), Error> {
111    unsafe {
112        // A C structure, supposed to be memset to 0 before use.
113        let mut action: libc::sigaction = mem::zeroed();
114        #[cfg(target_os = "aix")]
115        {
116            action.sa_union.__su_sigaction = mem::transmute::<
117                usize,
118                extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void),
119            >(libc::SIG_DFL);
120        }
121        #[cfg(not(target_os = "aix"))]
122        { action.sa_sigaction = libc::SIG_DFL as _; }
123        if libc::sigaction(signal, &action, ptr::null_mut()) == 0 {
124            Ok(())
125        } else {
126            Err(Error::last_os_error())
127        }
128    }
129}
130
131#[cfg(windows)]
132fn restore_default(signal: c_int) -> Result<(), Error> {
133    unsafe {
134        // SIG_DFL = 0, but not in libc :-(
135        if libc::signal(signal, 0) == 0 {
136            Ok(())
137        } else {
138            Err(Error::last_os_error())
139        }
140    }
141}
142
143/// Emulates the behaviour of a default handler for the provided signal.
144///
145/// This function does its best to provide the same action as the default handler would do, without
146/// disrupting the rest of the handling of such signal in the application. It is also
147/// async-signal-safe.
148///
149/// This function necessarily looks up the appropriate action in a table. That means it is possible
150/// your system has a signal that is not known to this function. In such case an error is returned
151/// (equivalent of `EINVAL`).
152///
153/// See also the [`register_conditional_default`][crate::flag::register_conditional_default].
154///
155/// # Warning
156///
157/// There's a short race condition in case of signals that terminate (either with or without a core
158/// dump). The emulation first resets the signal handler back to default (as the application is
159/// going to end, it's not a problem) and invokes it. But if some other thread installs a signal
160/// handler in the meantime (without assistance from `signal-hook`), it can happen this will be
161/// invoked by the re-raised signal.
162///
163/// This function will still terminate the application (there's a fallback on `abort`), the risk is
164/// invoking the newly installed signal handler. Note that manipulating the low-level signals is
165/// always racy in a multi-threaded program, therefore the described situation is already
166/// discouraged.
167///
168/// If you are uneasy about such race condition, the recommendation is to run relevant termination
169/// routine manually ([`exit`][super::exit] or [`abort`][super::abort]); they always do what they
170/// say, but slightly differ in externally observable behaviour from termination by a signal (the
171/// exit code will specify that the application exited, not that it terminated with a signal in the
172/// first case, and `abort` terminates on `SIGABRT`, so the detected termination signal may be
173/// different).
174pub fn emulate_default_handler(signal: c_int) -> Result<(), Error> {
175    #[cfg(not(windows))]
176    {
177        if signal == SIGSTOP || signal == SIGKILL {
178            return low_level::raise(signal);
179        }
180    }
181    let kind = DETAILS
182        .iter()
183        .find(|d| d.signal == signal)
184        .map(|d| d.default_kind)
185        .ok_or_else(|| Error::from_raw_os_error(EINVAL))?;
186    match kind {
187        DefaultKind::Ignore => Ok(()),
188        #[cfg(not(windows))]
189        DefaultKind::Stop => low_level::raise(SIGSTOP),
190        DefaultKind::Term => {
191            if let Ok(()) = restore_default(signal) {
192                #[cfg(not(windows))]
193                unsafe {
194                    #[allow(deprecated)]
195                    let mut newsigs: sigset_t = mem::zeroed();
196
197                    // Some android versions don't have the sigemptyset and sigaddset.
198                    // Unfortunately, we don't have an access to the android _version_. We just
199                    // know that 64bit versions are all OK, so this is a best-effort guess.
200                    //
201                    // For the affected/guessed versions, we provide our own implementation. We
202                    // hope it to be correct (it's inspired by a libc implementation and we assume
203                    // the kernel uses the same format ‒ it's unlikely to be different both because
204                    // of compatibility and because there's really nothing to invent about a
205                    // bitarray).
206                    //
207                    // We use the proper way for other systems.
208                    #[cfg(all(target_os = "android", target_pointer_width = "32"))]
209                    unsafe fn prepare_sigset(set: *mut sigset_t, mut signal: c_int) {
210                        signal -= 1;
211                        let set_raw: *mut libc::c_ulong = set.cast();
212                        let size = mem::size_of::<libc::c_ulong>();
213                        assert_eq!(set_raw as usize % mem::align_of::<libc::c_ulong>(), 0);
214                        let pos = signal as usize / size;
215                        assert!(pos < mem::size_of::<sigset_t>() / size);
216                        let bit = 1 << (signal as usize % size);
217                        set_raw.add(pos).write(bit);
218                    }
219
220                    #[cfg(not(all(target_os = "android", target_pointer_width = "32")))]
221                    unsafe fn prepare_sigset(set: *mut sigset_t, signal: c_int) {
222                        libc::sigemptyset(set);
223                        libc::sigaddset(set, signal);
224                    }
225
226                    prepare_sigset(&mut newsigs, signal);
227                    // Ignore the result, if it doesn't work, we try anyway
228                    // Also, sigprocmask is unspecified, but available on more systems. And we want
229                    // to just enable _something_. And if it doesn't work, we'll terminate
230                    // anyway... It's not UB, so we are good.
231                    libc::sigprocmask(SIG_UNBLOCK, &newsigs, ptr::null_mut());
232                }
233                let _ = low_level::raise(signal);
234            }
235            // Fallback if anything failed or someone managed to put some other action in in
236            // between.
237            unsafe { libc::abort() }
238        }
239    }
240}
241
242#[cfg(test)]
243mod test {
244    use super::*;
245
246    #[test]
247    fn existing() {
248        assert_eq!("SIGTERM", signal_name(SIGTERM).unwrap());
249    }
250
251    #[test]
252    fn unknown() {
253        assert!(signal_name(128).is_none());
254    }
255}