| 1 | //! Providing auxiliary information for signals. |
| 2 | |
| 3 | use std::io::Error; |
| 4 | use std::mem; |
| 5 | use std::ptr; |
| 6 | |
| 7 | use libc::{c_int, EINVAL}; |
| 8 | #[cfg (not(windows))] |
| 9 | use libc::{sigset_t, SIG_UNBLOCK}; |
| 10 | |
| 11 | use crate::consts::signal::*; |
| 12 | use crate::low_level; |
| 13 | |
| 14 | #[derive (Clone, Copy, Debug)] |
| 15 | enum DefaultKind { |
| 16 | Ignore, |
| 17 | #[cfg (not(windows))] |
| 18 | Stop, |
| 19 | Term, |
| 20 | } |
| 21 | |
| 22 | struct Details { |
| 23 | signal: c_int, |
| 24 | name: &'static str, |
| 25 | default_kind: DefaultKind, |
| 26 | } |
| 27 | |
| 28 | macro_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))] |
| 39 | const 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)] |
| 84 | const 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 | /// ``` |
| 105 | pub fn signal_name(signal: c_int) -> Option<&'static str> { |
| 106 | DETAILS.iter().find(|d: &&Details| d.signal == signal).map(|d: &Details| d.name) |
| 107 | } |
| 108 | |
| 109 | #[cfg (not(windows))] |
| 110 | fn 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(signum:signal, &action, oldact:ptr::null_mut()) == 0 { |
| 124 | Ok(()) |
| 125 | } else { |
| 126 | Err(Error::last_os_error()) |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | #[cfg (windows)] |
| 132 | fn 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). |
| 174 | pub 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)] |
| 243 | mod 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 | } |
| 256 | |