1//! Asynchronous signal handling.
2//!
3//! This crate provides the [`Signals`] type, which can be used to listen for POSIX signals asynchronously.
4//! It can be seen as an asynchronous version of [`signal_hook::iterator::Signals`].
5//!
6//! [`signal_hook::iterator::Signals`]: https://docs.rs/signal-hook/latest/signal_hook/iterator/struct.Signals.html
7//!
8//! # Implementation
9//!
10//! This crate uses the [`signal_hook_registry`] crate to register a listener for each signal. That
11//! listener will then send a message through a Unix socket to the [`Signals`] type, which will
12//! receive it and notify the user. Asynchronous notification is done through the [`async-io`] crate.
13//!
14//! Note that the internal pipe has a limited capacity. Once it has reached capacity, additional
15//! signals will be dropped.
16//!
17//! On Windows, a different implementation that only supports `SIGINT` is used. This implementation
18//! uses a channel to notify the user.
19//!
20//! [`signal_hook_registry`]: https://crates.io/crates/signal-hook-registry
21//! [`async-io`]: https://crates.io/crates/async-io
22//!
23//! # Examples
24//!
25//! ```no_run
26//! use async_signal::{Signal, Signals};
27//! use futures_lite::prelude::*;
28//! use signal_hook::low_level;
29//!
30//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
31//! # async_io::block_on(async {
32//! // Register the signals we want to receive.
33//! let mut signals = Signals::new(&[
34//! Signal::Term,
35//! Signal::Quit,
36//! Signal::Int,
37//! ])?;
38//!
39//! // Wait for a signal to be received.
40//! while let Some(signal) = signals.next().await {
41//! // Print the signal.
42//! eprintln!("Received signal {:?}", signal);
43//!
44//! // After printing it, do whatever the signal was supposed to do in the first place.
45//! low_level::emulate_default_handler(signal.unwrap() as i32).unwrap();
46//! }
47//! # Ok(())
48//! # })
49//! # }
50//! ```
51
52#![doc(
53 html_favicon_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
54)]
55#![doc(
56 html_logo_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
57)]
58
59cfg_if::cfg_if! {
60 if #[cfg(windows)] {
61 mod channel;
62 use channel as sys;
63 } else {
64 mod pipe;
65 use pipe as sys;
66 }
67}
68
69cfg_if::cfg_if! {
70 if #[cfg(unix)] {
71 use signal_hook_registry as registry;
72 } else if #[cfg(windows)] {
73 mod windows_registry;
74 use windows_registry as registry;
75 }
76}
77
78use futures_core::ready;
79use futures_core::stream::Stream;
80use registry::SigId;
81
82use std::borrow::Borrow;
83use std::collections::HashMap;
84use std::fmt;
85use std::io;
86use std::pin::Pin;
87use std::task::{Context, Poll};
88
89#[cfg(unix)]
90use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
91
92mod signum {
93 pub(crate) use std::os::raw::c_int;
94
95 macro_rules! sig {
96 ($rustix_name:ident, $raw_value:literal) => {{
97 #[cfg(unix)]
98 {
99 rustix::process::Signal::$rustix_name as c_int
100 }
101
102 #[cfg(windows)]
103 {
104 $raw_value
105 }
106 }};
107 }
108
109 // Define these ourselves.
110 pub const SIGHUP: c_int = sig!(Hup, 1);
111 pub const SIGINT: c_int = sig!(Int, 2);
112 pub const SIGQUIT: c_int = sig!(Quit, 3);
113 pub const SIGILL: c_int = sig!(Ill, 4);
114 pub const SIGTRAP: c_int = sig!(Trap, 5);
115 pub const SIGABRT: c_int = sig!(Abort, 6);
116 pub const SIGFPE: c_int = sig!(Fpe, 8);
117 pub const SIGKILL: c_int = sig!(Kill, 9);
118 pub const SIGSEGV: c_int = sig!(Segv, 11);
119 pub const SIGPIPE: c_int = sig!(Pipe, 13);
120 pub const SIGALRM: c_int = sig!(Alarm, 14);
121 pub const SIGTERM: c_int = sig!(Term, 15);
122 pub const SIGTTIN: c_int = sig!(Ttin, 21);
123 pub const SIGTTOU: c_int = sig!(Ttou, 22);
124 pub const SIGXCPU: c_int = sig!(Xcpu, 24);
125 pub const SIGXFSZ: c_int = sig!(Xfsz, 25);
126 pub const SIGVTALRM: c_int = sig!(Vtalarm, 26);
127 pub const SIGPROF: c_int = sig!(Prof, 27);
128 pub const SIGWINCH: c_int = sig!(Winch, 28);
129 pub const SIGCHLD: c_int = sig!(Child, 17);
130 pub const SIGBUS: c_int = sig!(Bus, 7);
131 pub const SIGUSR1: c_int = sig!(Usr1, 10);
132 pub const SIGUSR2: c_int = sig!(Usr2, 12);
133 pub const SIGCONT: c_int = sig!(Cont, 18);
134 pub const SIGSTOP: c_int = sig!(Stop, 19);
135 pub const SIGTSTP: c_int = sig!(Tstp, 20);
136 pub const SIGURG: c_int = sig!(Urg, 23);
137 pub const SIGIO: c_int = sig!(Io, 29);
138 pub const SIGSYS: c_int = sig!(Sys, 31);
139}
140
141macro_rules! define_signal_enum {
142 (
143 $(#[$outer:meta])*
144 pub enum Signal {
145 $(
146 $(#[$inner:meta])*
147 $name:ident = $value:ident,
148 )*
149 }
150 ) => {
151 $(#[$outer])*
152 #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
153 #[repr(i32)]
154 pub enum Signal {
155 $(
156 $(#[$inner])*
157 $name = signum::$value,
158 )*
159 }
160
161 impl Signal {
162 /// Returns the signal number.
163 fn number(self) -> std::os::raw::c_int {
164 match self {
165 $(
166 Signal::$name => signum::$value,
167 )*
168 }
169 }
170
171 /// Parse a signal from its number.
172 #[cfg(unix)]
173 fn from_number(number: std::os::raw::c_int) -> Option<Self> {
174 match number {
175 $(
176 signum::$value => Some(Signal::$name),
177 )*
178 _ => None,
179 }
180 }
181 }
182 }
183}
184
185define_signal_enum! {
186 // Copied from https://github.com/bytecodealliance/rustix/blob/main/src/backend/linux_raw/process/types.rs#L81-L161
187
188 /// The signal types that we are able to listen for.
189 pub enum Signal {
190 /// `SIGHUP`
191 Hup = SIGHUP,
192 /// `SIGINT`
193 Int = SIGINT,
194 /// `SIGQUIT`
195 Quit = SIGQUIT,
196 /// `SIGILL`
197 Ill = SIGILL,
198 /// `SIGTRAP`
199 Trap = SIGTRAP,
200 /// `SIGABRT`, aka `SIGIOT`
201 #[doc(alias = "Iot")]
202 #[doc(alias = "Abrt")]
203 Abort = SIGABRT,
204 /// `SIGBUS`
205 Bus = SIGBUS,
206 /// `SIGFPE`
207 Fpe = SIGFPE,
208 /// `SIGKILL`
209 Kill = SIGKILL,
210 /// `SIGUSR1`
211 Usr1 = SIGUSR1,
212 /// `SIGSEGV`
213 Segv = SIGSEGV,
214 /// `SIGUSR2`
215 Usr2 = SIGUSR2,
216 /// `SIGPIPE`
217 Pipe = SIGPIPE,
218 /// `SIGALRM`
219 #[doc(alias = "Alrm")]
220 Alarm = SIGALRM,
221 /// `SIGTERM`
222 Term = SIGTERM,
223 /// `SIGCHLD`
224 #[doc(alias = "Chld")]
225 Child = SIGCHLD,
226 /// `SIGCONT`
227 Cont = SIGCONT,
228 /// `SIGSTOP`
229 Stop = SIGSTOP,
230 /// `SIGTSTP`
231 Tstp = SIGTSTP,
232 /// `SIGTTIN`
233 Ttin = SIGTTIN,
234 /// `SIGTTOU`
235 Ttou = SIGTTOU,
236 /// `SIGURG`
237 Urg = SIGURG,
238 /// `SIGXCPU`
239 Xcpu = SIGXCPU,
240 /// `SIGXFSZ`
241 Xfsz = SIGXFSZ,
242 /// `SIGVTALRM`
243 #[doc(alias = "Vtalrm")]
244 Vtalarm = SIGVTALRM,
245 /// `SIGPROF`
246 Prof = SIGPROF,
247 /// `SIGWINCH`
248 Winch = SIGWINCH,
249 /// `SIGIO`, aka `SIGPOLL`
250 #[doc(alias = "Poll")]
251 Io = SIGIO,
252 /// `SIGSYS`, aka `SIGUNUSED`
253 #[doc(alias = "Unused")]
254 Sys = SIGSYS,
255 }
256}
257
258/// Wait for a specific set of signals.
259///
260/// See the [module-level documentation](index.html) for more details.
261pub struct Signals {
262 /// The strategy used to read the signals.
263 notifier: sys::Notifier,
264
265 /// The map between signal numbers and signal IDs.
266 signal_ids: HashMap<Signal, SigId>,
267}
268
269impl Drop for Signals {
270 fn drop(&mut self) {
271 for signal: &SigId in self.signal_ids.values() {
272 registry::unregister(*signal);
273 }
274 }
275}
276
277impl fmt::Debug for Signals {
278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279 struct RegisteredSignals<'a>(&'a HashMap<Signal, SigId>);
280
281 impl fmt::Debug for RegisteredSignals<'_> {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 f.debug_set().entries(self.0.keys()).finish()
284 }
285 }
286
287 f&mut DebugStruct<'_, '_>.debug_struct("Signals")
288 .field("notifier", &self.notifier)
289 .field(name:"signal_ids", &RegisteredSignals(&self.signal_ids))
290 .finish()
291 }
292}
293
294impl Signals {
295 /// Create a new `Signals` instance with a set of signals.
296 pub fn new<B>(signals: impl IntoIterator<Item = B>) -> io::Result<Self>
297 where
298 B: Borrow<Signal>,
299 {
300 let mut this = Self {
301 notifier: sys::Notifier::new()?,
302 signal_ids: HashMap::new(),
303 };
304
305 // Add the signals to the set of signals to wait for.
306 this.add_signals(signals)?;
307
308 Ok(this)
309 }
310
311 /// Add signals to the set of signals to wait for.
312 ///
313 /// One signal cannot be added twice. If a signal that has already been added is passed to this
314 /// method, it will be ignored.
315 ///
316 /// Registering a signal prevents the default behavior of that signal from occurring. For
317 /// example, if you register `SIGINT`, pressing `Ctrl+C` will no longer terminate the process.
318 /// To run the default signal handler, use [`signal_hook::low_level::emulate_default_handler`]
319 /// instead.
320 ///
321 /// [`signal_hook::low_level::emulate_default_handler`]: https://docs.rs/signal-hook/latest/signal_hook/low_level/fn.emulate_default_handler.html
322 pub fn add_signals<B>(&mut self, signals: impl IntoIterator<Item = B>) -> io::Result<()>
323 where
324 B: Borrow<Signal>,
325 {
326 for signal in signals {
327 let signal = signal.borrow();
328
329 // If we've already registered this signal, skip it.
330 if self.signal_ids.contains_key(signal) {
331 continue;
332 }
333
334 // Get the closure to call when the signal is received.
335 let closure = self.notifier.add_signal(*signal)?;
336
337 let id = unsafe {
338 // SAFETY: Closure is guaranteed to be signal-safe.
339 registry::register(signal.number(), closure)?
340 };
341
342 // Add the signal ID to the map.
343 self.signal_ids.insert(*signal, id);
344 }
345
346 Ok(())
347 }
348
349 /// Remove signals from the set of signals to wait for.
350 ///
351 /// This function can be used to opt out of listening to signals previously registered via
352 /// [`add_signals`](Self::add_signals) or [`new`](Self::new). If a signal that has not been
353 /// registered is passed to this method, it will be ignored.
354 pub fn remove_signals<B>(&mut self, signals: impl IntoIterator<Item = B>) -> io::Result<()>
355 where
356 B: Borrow<Signal>,
357 {
358 for signal in signals {
359 let signal = signal.borrow();
360
361 // If we haven't registered this signal, skip it.
362 let id = match self.signal_ids.remove(signal) {
363 Some(id) => id,
364 None => continue,
365 };
366
367 // Remove the signal from the notifier.
368 self.notifier.remove_signal(*signal)?;
369
370 // Use `signal-hook-registry` to unregister the signal.
371 registry::unregister(id);
372 }
373
374 Ok(())
375 }
376}
377
378#[cfg(unix)]
379impl AsRawFd for Signals {
380 fn as_raw_fd(&self) -> RawFd {
381 self.notifier.as_raw_fd()
382 }
383}
384
385#[cfg(unix)]
386impl AsFd for Signals {
387 fn as_fd(&self) -> BorrowedFd<'_> {
388 self.notifier.as_fd()
389 }
390}
391
392impl Unpin for Signals {}
393
394impl Stream for Signals {
395 type Item = io::Result<Signal>;
396
397 #[inline]
398 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
399 Pin::new(&mut &*self).poll_next(cx)
400 }
401
402 #[inline]
403 fn size_hint(&self) -> (usize, Option<usize>) {
404 // This stream is expected to never end.
405 (std::usize::MAX, None)
406 }
407}
408
409impl Stream for &Signals {
410 type Item = io::Result<Signal>;
411
412 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
413 let signal: Signal = ready!(self.notifier.poll_next(cx))?;
414 Poll::Ready(Some(Ok(signal)))
415 }
416
417 #[inline]
418 fn size_hint(&self) -> (usize, Option<usize>) {
419 // This stream is expected to never end.
420 (std::usize::MAX, None)
421 }
422}
423