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;