chan_signal/
lib.rs

1/*!
2This crate provides a simplistic interface to subscribe to operating system
3signals through a channel API. Use is extremely simple:
4
5```no_run
6use chan_signal::Signal;
7
8let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
9
10// Blocks until this process is sent an INT or TERM signal.
11// Since the channel is never closed, we can unwrap the received value.
12signal.recv().unwrap();
13```
14
15
16# Example
17
18When combined with `chan_select!` from the `chan` crate, one can easily
19integrate signals with the rest of your program. For example, consider a
20main function that waits for either normal completion of work (which is done
21in a separate thread) or for a signal to be delivered:
22
23```no_run
24#[macro_use]
25extern crate chan;
26extern crate chan_signal;
27
28use chan_signal::Signal;
29
30fn main() {
31    // Signal gets a value when the OS sent a INT or TERM signal.
32    let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
33    // When our work is complete, send a sentinel value on `sdone`.
34    let (sdone, rdone) = chan::sync(0);
35    // Run work.
36    ::std::thread::spawn(move || run(sdone));
37
38    // Wait for a signal or for work to be done.
39    chan_select! {
40        signal.recv() -> signal => {
41            println!("received signal: {:?}", signal)
42        },
43        rdone.recv() => {
44            println!("Program completed normally.");
45        }
46    }
47}
48
49fn run(_sdone: chan::Sender<()>) {
50    // Do some work.
51    ::std::thread::sleep_ms(1000);
52    // Quit normally.
53    // Note that we don't need to send any values. We just let the
54    // sending channel drop, which closes the channel, which causes
55    // the receiver to synchronize immediately and always.
56}
57```
58
59You can see this example in action by running `cargo run --example select`
60in the root directory of this crate's
61[repository](https://github.com/BurntSushi/chan-signal).
62
63# Platform support (no Windows support)
64
65This should work on Unix platforms supported by Rust itself.
66
67There is no Windows support at all. I welcome others to either help me add it
68or help educate me so that I may one day add it.
69
70
71# How it works
72
73Overview: uses the "spawn a thread and block on `sigwait`" approach. In
74particular, it avoids standard asynchronous signal handling because it is
75very difficult to do anything non-trivial inside a signal handler.
76
77After a call to `notify`/`notify_on` (or `block`), the given signals are set
78to *blocked*. This is necessary for synchronous signal handling using `sigwait`.
79
80After the first call to `notify` (or `notify_on`), a new thread is spawned and
81immediately blocks on a call to `sigwait`. It is only unblocked when one of the
82signals that were masked previously by calls to `notify` etc. arrives, which now
83cannot be delivered directly to any of the threads of the process, and therefore
84unblocks the waiting signal watcher thread. Once it's unblocked, it sends the
85signal on all subscribed channels via a non-blocking send. Once all channels
86have been visited, the thread blocks on `sigwait` again.
87
88This approach has some restrictions. Namely, your program must comply with the
89following:
90
91* Any and all threads spawned in your program **must** come after the first
92  call to `notify` (or `notify_on`). This is so all spawned threads inherit
93  the blocked status of signals. If a thread starts before `notify` is called,
94  it will not have the correct signal mask. When a signal is delivered, the
95  result is indeterminate.
96* No other threads may call `sigwait`. When a signal is delivered, only one
97  `sigwait` is indeterminately unblocked.
98
99
100# Future work
101
102This crate exposes the simplest API I could think of. As a result, a few
103additions may be warranted:
104
105* Expand the set of signals. (Requires figuring out platform differences.)
106* Allow channel unsubscription.
107* Allow callers to reset the signal mask? (Seems hard.)
108* Support Windows.
109*/
110#![deny(missing_docs)]
111
112extern crate bit_set;
113#[macro_use] extern crate chan;
114#[macro_use] extern crate lazy_static;
115extern crate libc;
116
117use std::collections::HashMap;
118use std::io;
119use std::mem;
120use std::ptr;
121use std::sync::Mutex;
122use std::thread;
123
124use bit_set::BitSet;
125use chan::Sender;
126use libc::{
127    // POSIX.1-2008, minus SIGPOLL (not in some BSD, use SIGIO)
128    SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGKILL,
129    SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2,
130    SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU,
131    SIGBUS, SIGPROF, SIGSYS, SIGTRAP, SIGURG, SIGVTALRM,
132    SIGXCPU, SIGXFSZ,
133
134    // Common Extensions (SIGINFO and SIGEMT not in libc)
135    SIGIO,
136    SIGWINCH,
137
138    SIG_BLOCK,
139    SIG_SETMASK,
140};
141use libc::kill;
142use libc::getpid;
143
144lazy_static! {
145    static ref HANDLERS: Mutex<HashMap<Sender<Signal>, BitSet>> = {
146        init();
147        Mutex::new(HashMap::new())
148    };
149}
150
151/// Create a new channel subscribed to the given signals.
152///
153/// The channel returned is never closed.
154///
155/// This is a convenience function for subscribing to multiple signals at once.
156/// See the documentation of `notify_on` for details.
157///
158/// The channel returned has a small buffer to prevent signals from being
159/// dropped.
160///
161/// **THIS MUST BE CALLED BEFORE ANY OTHER THREADS ARE SPAWNED IN YOUR
162/// PROCESS.**
163///
164/// # Example
165///
166/// ```no_run
167/// use chan_signal::Signal;
168///
169/// let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
170///
171/// // Blocks until this process is sent an INT or TERM signal.
172/// // Since the channel is never closed, we can unwrap the received value.
173/// signal.recv().unwrap();
174/// ```
175pub fn notify(signals: &[Signal]) -> chan::Receiver<Signal> {
176    let (s, r) = chan::sync(100);
177    for &sig in signals {
178        notify_on(&s, sig);
179    }
180    // dropping `s` is OK because `notify_on` acquires one.
181    r
182}
183
184/// Subscribe to a signal on a channel.
185///
186/// When `signal` is delivered to this process, it will be sent on the channel
187/// given.
188///
189/// Note that a signal is sent using a non-blocking send. Namely, if the
190/// channel's buffer is full (or it has no buffer) and it isn't ready to
191/// rendezvous, then the signal will be dropped.
192///
193/// There is currently no way to unsubscribe. Moreover, the channel given
194/// here will be alive for the lifetime of the process. Therefore, the channel
195/// will never be closed.
196///
197/// **THIS MUST BE CALLED BEFORE ANY OTHER THREADS ARE SPAWNED IN YOUR
198/// PROCESS.**
199pub fn notify_on(chan: &Sender<Signal>, signal: Signal) {
200    let mut subs = HANDLERS.lock().unwrap();
201    if subs.contains_key(chan) {
202        subs.get_mut(chan).unwrap().insert(signal.as_sig() as usize);
203    } else {
204        let mut sigs = BitSet::new();
205        sigs.insert(signal.as_sig() as usize);
206        subs.insert((*chan).clone(), sigs);
207    }
208
209    // Make sure that the signal that we want notifications on is blocked
210    // It does not matter if we block the same signal twice.
211    block(&[signal]);
212}
213
214/// Block all given signals without receiving notifications.
215///
216/// If a signal has also been passed to `notify`/`notify_on` this function
217/// does not have any effect in terms of that signal.
218///
219/// **THIS MUST BE CALLED BEFORE ANY OTHER THREADS ARE SPAWNED IN YOUR
220/// PROCESS.**
221pub fn block(signals: &[Signal]) {
222    let mut block = SigSet::empty();
223    for signal in signals {
224        block.add(signal.as_sig()).unwrap();
225    }
226    block.thread_block_signals().unwrap();
227}
228
229/// Block all subscribable signals.
230///
231/// Calling this function effectively restores the default behavior of
232/// version <= 0.2.0 of this library.
233///
234/// **THIS MUST BE CALLED BEFORE ANY OTHER THREADS ARE SPAWNED IN YOUR
235/// PROCESS.**
236pub fn block_all_subscribable() {
237    SigSet::subscribable().thread_block_signals().unwrap();
238}
239
240fn init() {
241    // First:
242    // Get the curren thread_mask. (We cannot just overwrite the threadmask with
243    // an empty one because this function is executed lazily.
244    let saved_mask = SigSet::current().unwrap();
245
246    // Then:
247    // Block all signals in this thread. The signal mask will then be inherited
248    // by the worker thread.
249    SigSet::subscribable().thread_set_signal_mask().unwrap();
250    thread::spawn(move || {
251        let mut listen = SigSet::subscribable();
252
253        loop {
254            let sig = listen.wait().unwrap();
255            let subs = HANDLERS.lock().unwrap();
256            for (s, sigs) in subs.iter() {
257                if !sigs.contains(sig as usize) {
258                    continue;
259                }
260                chan_select! {
261                    default => {},
262                    s.send(Signal::new(sig)) => {},
263                }
264            }
265        }
266    });
267
268    // Now:
269    // Reset to the previously saved sigmask.
270    // This whole procedure is necessary, as we cannot rely on the worker thread
271    // starting fast enough to set its signal mask. Otherwise an early SIGTERM or
272    // similar may take down the process even though the main thread has blocked
273    // the signal.
274    saved_mask.thread_set_signal_mask().unwrap();
275}
276
277/// Kill the current process. (Only used in tests.)
278#[doc(hidden)]
279pub fn kill_this(sig: Signal) {
280    unsafe { kill(getpid(), sig.as_sig()); }
281}
282
283type Sig = libc::c_int;
284
285/// The set of subscribable signals.
286///
287/// After the first call to `notify_on` (or `notify`), precisely this set of
288/// signals are set to blocked status.
289#[allow(missing_docs)]
290#[derive(Clone, Copy, Debug, Eq, PartialEq)]
291pub enum Signal {
292    HUP,
293    INT,
294    QUIT,
295    ILL,
296    ABRT,
297    FPE,
298    KILL,
299    SEGV,
300    PIPE,
301    ALRM,
302    TERM,
303    USR1,
304    USR2,
305    CHLD,
306    CONT,
307    STOP,
308    TSTP,
309    TTIN,
310    TTOU,
311    BUS,
312    PROF,
313    SYS,
314    TRAP,
315    URG,
316    VTALRM,
317    XCPU,
318    XFSZ,
319    IO,
320    WINCH,
321    #[doc(hidden)]
322    __NonExhaustiveMatch,
323}
324
325impl Signal {
326    fn new(sig: Sig) -> Signal {
327        match sig {
328            SIGHUP => Signal::HUP,
329            SIGINT => Signal::INT,
330            SIGQUIT => Signal::QUIT,
331            SIGILL => Signal::ILL,
332            SIGABRT => Signal::ABRT,
333            SIGFPE => Signal::FPE,
334            SIGKILL => Signal::KILL,
335            SIGSEGV => Signal::SEGV,
336            SIGPIPE => Signal::PIPE,
337            SIGALRM => Signal::ALRM,
338            SIGTERM => Signal::TERM,
339            SIGUSR1 => Signal::USR1,
340            SIGUSR2 => Signal::USR2,
341            SIGCHLD => Signal::CHLD,
342            SIGCONT => Signal::CONT,
343            SIGSTOP => Signal::STOP,
344            SIGTSTP => Signal::TSTP,
345            SIGTTIN => Signal::TTIN,
346            SIGTTOU => Signal::TTOU,
347            SIGBUS => Signal::BUS,
348            SIGPROF => Signal::PROF,
349            SIGSYS => Signal::SYS,
350            SIGTRAP => Signal::TRAP,
351            SIGURG => Signal::URG,
352            SIGVTALRM => Signal::VTALRM,
353            SIGXCPU => Signal::XCPU,
354            SIGXFSZ => Signal::XFSZ,
355            SIGIO => Signal::IO,
356            SIGWINCH => Signal::WINCH,
357            sig => panic!("unsupported signal number: {}", sig),
358        }
359    }
360
361    fn as_sig(self) -> Sig {
362        match self {
363            Signal::HUP => SIGHUP,
364            Signal::INT => SIGINT,
365            Signal::QUIT => SIGQUIT,
366            Signal::ILL => SIGILL,
367            Signal::ABRT => SIGABRT,
368            Signal::FPE => SIGFPE,
369            Signal::KILL => SIGKILL,
370            Signal::SEGV => SIGSEGV,
371            Signal::PIPE => SIGPIPE,
372            Signal::ALRM => SIGALRM,
373            Signal::TERM => SIGTERM,
374            Signal::USR1 => SIGUSR1,
375            Signal::USR2 => SIGUSR2,
376            Signal::CHLD => SIGCHLD,
377            Signal::CONT => SIGCONT,
378            Signal::STOP => SIGSTOP,
379            Signal::TSTP => SIGTSTP,
380            Signal::TTIN => SIGTTIN,
381            Signal::TTOU => SIGTTOU,
382            Signal::BUS => SIGBUS,
383            Signal::PROF => SIGPROF,
384            Signal::SYS => SIGSYS,
385            Signal::TRAP => SIGTRAP,
386            Signal::URG => SIGURG,
387            Signal::VTALRM => SIGVTALRM,
388            Signal::XCPU => SIGXCPU,
389            Signal::XFSZ => SIGXFSZ,
390            Signal::IO => SIGIO,
391            Signal::WINCH => SIGWINCH,
392            Signal::__NonExhaustiveMatch => unreachable!(),
393        }
394    }
395}
396
397/// Safe wrapper around `sigset_t`.
398struct SigSet(sigset_t);
399
400impl SigSet {
401    fn empty() -> SigSet {
402        let mut set = unsafe { mem::zeroed() };
403        unsafe { sigemptyset(&mut set) };
404        SigSet(set)
405    }
406
407    fn current() -> io::Result<SigSet> {
408        let mut set = unsafe { mem::zeroed() };
409        let ecode = unsafe {
410            pthread_sigmask(SIG_SETMASK, ptr::null_mut(), &mut set)
411        };
412        ok_errno(SigSet(set), ecode)
413    }
414
415    /// Creates a new signal set with precisely the signals we're limited
416    /// to subscribing to.
417    fn subscribable() -> SigSet {
418        let mut set = SigSet::empty();
419        set.add(SIGHUP).unwrap();
420        set.add(SIGINT).unwrap();
421        set.add(SIGQUIT).unwrap();
422        set.add(SIGILL).unwrap();
423        set.add(SIGABRT).unwrap();
424        set.add(SIGFPE).unwrap();
425        set.add(SIGKILL).unwrap();
426        set.add(SIGSEGV).unwrap();
427        set.add(SIGPIPE).unwrap();
428        set.add(SIGALRM).unwrap();
429        set.add(SIGTERM).unwrap();
430        set.add(SIGUSR1).unwrap();
431        set.add(SIGUSR2).unwrap();
432        set.add(SIGCHLD).unwrap();
433        set.add(SIGCONT).unwrap();
434        set.add(SIGSTOP).unwrap();
435        set.add(SIGTSTP).unwrap();
436        set.add(SIGTTIN).unwrap();
437        set.add(SIGTTOU).unwrap();
438        set.add(SIGBUS).unwrap();
439        set.add(SIGPROF).unwrap();
440        set.add(SIGSYS).unwrap();
441        set.add(SIGTRAP).unwrap();
442        set.add(SIGURG).unwrap();
443        set.add(SIGVTALRM,).unwrap();
444        set.add(SIGXCPU).unwrap();
445        set.add(SIGXFSZ).unwrap();
446        set.add(SIGIO).unwrap();
447        set.add(SIGWINCH).unwrap();
448        set
449    }
450
451    fn add(&mut self, sig: Sig) -> io::Result<()> {
452        unsafe { ok_errno((), sigaddset(&mut self.0, sig)) }
453    }
454
455    fn wait(&mut self) -> io::Result<Sig> {
456        let mut sig: Sig = 0;
457        let errno = unsafe { sigwait(&mut self.0, &mut sig) };
458        ok_errno(sig, errno)
459    }
460
461    fn thread_block_signals(&self) -> io::Result<()> {
462        let ecode = unsafe {
463            pthread_sigmask(SIG_BLOCK, &self.0, ptr::null_mut())
464        };
465        ok_errno((), ecode)
466    }
467
468    fn thread_set_signal_mask(&self) -> io::Result<()> {
469        let ecode = unsafe {
470            pthread_sigmask(SIG_SETMASK, &self.0, ptr::null_mut())
471        };
472        ok_errno((), ecode)
473    }
474}
475
476fn ok_errno<T>(ok: T, ecode: libc::c_int) -> io::Result<T> {
477    if ecode != 0 { Err(io::Error::from_raw_os_error(ecode)) } else { Ok(ok) }
478}
479
480extern {
481    fn sigwait(set: *mut sigset_t, sig: *mut Sig) -> Sig;
482    fn sigaddset(set: *mut sigset_t, sig: Sig) -> libc::c_int;
483    fn sigemptyset(set: *mut sigset_t) -> libc::c_int;
484    fn pthread_sigmask(
485        how: libc::c_int,
486        set: *const sigset_t,
487        oldset: *mut sigset_t,
488    ) -> libc::c_int;
489}
490
491// Most of this was lifted out of rust-lang:rust/src/libstd/sys/unix/c.rs.
492
493#[cfg(all(target_os = "linux", target_pointer_width = "32"))]
494#[repr(C)]
495struct sigset_t {
496    __val: [libc::c_ulong; 32],
497}
498
499#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
500#[repr(C)]
501struct sigset_t {
502    __val: [libc::c_ulong; 16],
503}
504
505#[cfg(target_os = "android")]
506type sigset_t = libc::c_ulong;
507
508#[cfg(any(target_os = "macos", target_os = "ios"))]
509type sigset_t = u32;
510
511#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
512#[repr(C)]
513struct sigset_t {
514    bits: [u32; 4],
515}
516
517#[cfg(any(target_os = "bitrig", target_os = "netbsd", target_os = "openbsd"))]
518type sigset_t = libc::c_uint;