commandspec/
signal.rs

1// From https://raw.githubusercontent.com/watchexec/watchexec/master/src/signal.rs
2#![allow(unused)]
3
4use std::sync::Mutex;
5use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
6
7lazy_static! {
8    static ref CLEANUP: Mutex<Option<Box<Fn(self::Signal) + Send>>> = Mutex::new(None);
9}
10
11#[cfg(unix)]
12pub use nix::sys::signal::Signal;
13
14#[cfg(windows)]
15use winapi;
16
17// This is a dummy enum for Windows
18#[cfg(windows)]
19#[derive(Debug, Copy, Clone)]
20pub enum Signal {
21    SIGKILL,
22    SIGTERM,
23    SIGINT,
24    SIGHUP,
25    SIGSTOP,
26    SIGCONT,
27    SIGCHLD,
28    SIGUSR1,
29    SIGUSR2,
30}
31
32#[cfg(unix)]
33use nix::libc::*;
34
35#[cfg(unix)]
36pub trait ConvertToLibc {
37    fn convert_to_libc(self) -> c_int;
38}
39
40#[cfg(unix)]
41impl ConvertToLibc for Signal {
42    fn convert_to_libc(self) -> c_int {
43        // Convert from signal::Signal enum to libc::* c_int constants
44        match self {
45            Signal::SIGKILL => SIGKILL,
46            Signal::SIGTERM => SIGTERM,
47            Signal::SIGINT => SIGINT,
48            Signal::SIGHUP => SIGHUP,
49            Signal::SIGSTOP => SIGSTOP,
50            Signal::SIGCONT => SIGCONT,
51            Signal::SIGCHLD => SIGCHLD,
52            Signal::SIGUSR1 => SIGUSR1,
53            Signal::SIGUSR2 => SIGUSR2,
54            _ => panic!("unsupported signal: {:?}", self),
55        }
56    }
57}
58
59pub fn new(signal_name: Option<String>) -> Option<Signal> {
60    if let Some(signame) = signal_name {
61        let signal = match signame.as_ref() {
62            "SIGKILL" | "KILL" => Signal::SIGKILL,
63            "SIGTERM" | "TERM" => Signal::SIGTERM,
64            "SIGINT" | "INT" => Signal::SIGINT,
65            "SIGHUP" | "HUP" => Signal::SIGHUP,
66            "SIGSTOP" | "STOP" => Signal::SIGSTOP,
67            "SIGCONT" | "CONT" => Signal::SIGCONT,
68            "SIGCHLD" | "CHLD" => Signal::SIGCHLD,
69            "SIGUSR1" | "USR1" => Signal::SIGUSR1,
70            "SIGUSR2" | "USR2" => Signal::SIGUSR2,
71            _ => panic!("unsupported signal: {}", signame),
72        };
73
74        Some(signal)
75    } else {
76        None
77    }
78}
79
80static GLOBAL_HANDLER_ID: AtomicUsize = ATOMIC_USIZE_INIT;
81
82#[cfg(unix)]
83pub fn uninstall_handler() {
84    GLOBAL_HANDLER_ID.fetch_add(1, Ordering::Relaxed) + 1;
85
86    use nix::libc::c_int;
87    use nix::sys::signal::*;
88    use nix::unistd::Pid;
89
90    // Interrupt self to interrupt handler.
91    kill(Pid::this(), Signal::SIGUSR2);
92}
93
94#[cfg(unix)]
95pub fn install_handler<F>(handler: F)
96where
97    F: Fn(self::Signal) + 'static + Send + Sync,
98{
99    use nix::libc::c_int;
100    use nix::sys::signal::*;
101    use std::thread;
102
103    // Mask all signals interesting to us. The mask propagates
104    // to all threads started after this point.
105    let mut mask = SigSet::empty();
106    mask.add(SIGKILL);
107    mask.add(SIGTERM);
108    mask.add(SIGINT);
109    mask.add(SIGHUP);
110    mask.add(SIGSTOP);
111    mask.add(SIGCONT);
112    mask.add(SIGCHLD);
113    mask.add(SIGUSR1);
114    mask.add(SIGUSR2);
115    mask.thread_swap_mask(SigmaskHow::SIG_SETMASK).expect("unable to set signal mask");
116
117    set_handler(handler);
118
119    // Indicate interest in SIGCHLD by setting a dummy handler
120    pub extern "C" fn sigchld_handler(_: c_int) {}
121
122    unsafe {
123        let _ = sigaction(
124            SIGCHLD,
125            &SigAction::new(
126                SigHandler::Handler(sigchld_handler),
127                SaFlags::empty(),
128                SigSet::empty(),
129            ),
130        );
131    }
132
133    // Spawn a thread to catch these signals
134    let id = GLOBAL_HANDLER_ID.fetch_add(1, Ordering::Relaxed) + 1;
135    thread::spawn(move || {
136        let mut is_current = true;
137        while is_current {
138            let signal = mask.wait().expect("Unable to sigwait");
139            debug!("Received {:?}", signal);
140
141            if id != GLOBAL_HANDLER_ID.load(Ordering::Relaxed) {
142                return;
143            }
144            // Invoke closure
145            invoke(signal);
146
147            // Restore default behavior for received signal and unmask it
148            if signal != SIGCHLD {
149                let default_action =
150                    SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty());
151
152                unsafe {
153                    let _ = sigaction(signal, &default_action);
154                }
155            }
156
157            let mut new_mask = SigSet::empty();
158            new_mask.add(signal);
159
160            // Re-raise with signal unmasked
161            let _ = new_mask.thread_unblock();
162            let _ = raise(signal);
163            let _ = new_mask.thread_block();
164        }
165    });
166}
167
168#[cfg(windows)]
169pub fn uninstall_handler() {
170    use kernel32::SetConsoleCtrlHandler;
171    use winapi::{BOOL, DWORD, FALSE, TRUE};
172
173    unsafe {
174        SetConsoleCtrlHandler(Some(ctrl_handler), FALSE);
175    }
176    debug!("Removed ConsoleCtrlHandler.");
177}
178
179#[cfg(windows)]
180pub unsafe extern "system" fn ctrl_handler(_: winapi::DWORD) -> winapi::BOOL {
181    invoke(self::Signal::SIGTERM);
182
183    winapi::FALSE
184}
185
186#[cfg(windows)]
187pub fn install_handler<F>(handler: F)
188where
189    F: Fn(self::Signal) + 'static + Send + Sync,
190{
191    use kernel32::SetConsoleCtrlHandler;
192    use winapi::{BOOL, DWORD, FALSE, TRUE};
193
194    set_handler(handler);
195
196    unsafe {
197        SetConsoleCtrlHandler(Some(ctrl_handler), TRUE);
198    }
199}
200
201fn invoke(sig: self::Signal) {
202    if let Some(ref handler) = *CLEANUP.lock().unwrap() {
203        handler(sig)
204    }
205}
206
207fn set_handler<F>(handler: F)
208where
209    F: Fn(self::Signal) + 'static + Send + Sync,
210{
211    *CLEANUP.lock().unwrap() = Some(Box::new(handler));
212}