Skip to main content

nuclei/signal/
mod.rs

1//! Signals: a stream of the deliveries of one signal (unix) or console
2//! control event (Windows), and [`ctrl_c`](crate::signal::ctrl_c).
3//!
4//! Each signal has a count of its deliveries, which the process's handler
5//! raises. A listener remembers the count it last saw: a poll that finds
6//! the count moved is one item, however many deliveries that was. A
7//! listener that waits registers its waker, then looks at the count once
8//! more; whoever dispatches looks at the count after the delivery raised
9//! it, then wakes the listeners of each signal whose count moved, so no
10//! delivery is missed (the register-then-look pattern of the sync module).
11//!
12//! On unix a signal handler may do little: it raises the count and writes
13//! to one wake descriptor of the process, never closed, which every shard
14//! with a listener reads through a pump task of its own and then
15//! dispatches (see `unix`). On Windows the console control handler runs on
16//! a thread of its own and dispatches itself.
17
18use core::task::{Context, Poll};
19use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
20use std::sync::{Arc, OnceLock};
21
22use crate::sync::atomic_waker::AtomicWaker;
23
24#[cfg(unix)]
25pub mod unix;
26#[cfg(windows)]
27pub mod windows;
28
29/// Signal numbers below this: every signal of every unix nuclei runs on,
30/// and every console control event.
31const SLOTS: usize = 65;
32
33/// One signal's deliveries.
34struct Slot {
35    /// Deliveries so far.
36    count: AtomicU64,
37    /// The count the last dispatch woke the listeners for.
38    dispatched: AtomicU64,
39    /// Listeners alive.
40    listeners: AtomicUsize,
41}
42
43static DELIVERIES: [Slot; SLOTS] = [const {
44    Slot {
45        count: AtomicU64::new(0),
46        dispatched: AtomicU64::new(0),
47        listeners: AtomicUsize::new(0),
48    }
49}; SLOTS];
50
51/// A listener as dispatch sees it: its signal and its waker.
52struct Waiter {
53    signum: usize,
54    waker: AtomicWaker,
55}
56
57/// Every listener alive, by id.
58fn waiters() -> &'static kovan_map::HashMap<u64, Arc<Waiter>> {
59    static WAITERS: OnceLock<kovan_map::HashMap<u64, Arc<Waiter>>> = OnceLock::new();
60    // 64 is the smallest table the map accepts.
61    WAITERS.get_or_init(|| kovan_map::HashMap::with_capacity(64))
62}
63
64/// A delivery of `signum`: what a handler does first. Only atomics, so a
65/// unix signal handler may call it.
66fn delivered(signum: usize) {
67    if let Some(slot) = DELIVERIES.get(signum) {
68        slot.count.fetch_add(1, Ordering::SeqCst);
69    }
70}
71
72/// Wakes the listeners of every signal whose count moved since the last
73/// dispatch. Never from a unix signal handler: a waker may do anything.
74fn dispatch() {
75    let mut moved = 0u128;
76    for (signum, slot) in DELIVERIES.iter().enumerate() {
77        let now = slot.count.load(Ordering::SeqCst);
78        if slot.dispatched.swap(now, Ordering::SeqCst) != now {
79            moved |= 1 << signum;
80        }
81    }
82    if moved == 0 {
83        return;
84    }
85    #[cfg(any(target_os = "linux", target_os = "android"))]
86    if moved & (1 << libc::SIGCHLD) != 0 {
87        crate::process::reap_fallback();
88    }
89    for (_, waiter) in waiters().iter() {
90        if moved & (1 << waiter.signum) != 0 {
91            waiter.waker.wake();
92        }
93    }
94}
95
96/// Whether `signum` has a listener alive.
97#[cfg(windows)]
98fn listened(signum: usize) -> bool {
99    DELIVERIES
100        .get(signum)
101        .is_some_and(|slot| slot.listeners.load(Ordering::SeqCst) > 0)
102}
103
104/// One listener of `signum`: the platform streams wrap it.
105struct Listener {
106    id: u64,
107    signum: usize,
108    seen: u64,
109    waiter: Arc<Waiter>,
110}
111
112impl Listener {
113    /// A listener of `signum` (below [`SLOTS`]), which sees the deliveries
114    /// from now on.
115    fn new(signum: usize) -> Self {
116        static NEXT: AtomicU64 = AtomicU64::new(0);
117        let id = NEXT.fetch_add(1, Ordering::Relaxed);
118        let slot = &DELIVERIES[signum];
119        slot.listeners.fetch_add(1, Ordering::SeqCst);
120        let waiter = Arc::new(Waiter {
121            signum,
122            waker: AtomicWaker::new(),
123        });
124        waiters().insert(id, Arc::clone(&waiter));
125        Self {
126            id,
127            signum,
128            seen: slot.count.load(Ordering::SeqCst),
129            waiter,
130        }
131    }
132
133    /// Ready once the signal was delivered since the last item; `wait`
134    /// makes sure something dispatches for this waiter.
135    fn poll_recv(&mut self, cx: &mut Context<'_>, wait: impl FnOnce()) -> Poll<Option<()>> {
136        let count = &DELIVERIES[self.signum].count;
137        if self.take(count.load(Ordering::SeqCst)) {
138            return Poll::Ready(Some(()));
139        }
140        self.waiter.waker.register(cx.waker());
141        wait();
142        if self.take(count.load(Ordering::SeqCst)) {
143            return Poll::Ready(Some(()));
144        }
145        Poll::Pending
146    }
147
148    fn take(&mut self, now: u64) -> bool {
149        let moved = now != self.seen;
150        self.seen = now;
151        moved
152    }
153}
154
155impl core::fmt::Debug for Listener {
156    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
157        f.debug_struct("Listener")
158            .field("signum", &self.signum)
159            .finish_non_exhaustive()
160    }
161}
162
163impl Drop for Listener {
164    fn drop(&mut self) {
165        waiters().remove(&self.id);
166        DELIVERIES[self.signum]
167            .listeners
168            .fetch_sub(1, Ordering::SeqCst);
169    }
170}
171
172/// Completes once the process gets a Ctrl-C: `SIGINT` on unix, the
173/// console's `CTRL_C_EVENT` on Windows. Listening starts with the first
174/// poll; on unix the default action of `SIGINT` (ending the process) is
175/// replaced for good from then on.
176///
177/// # Errors
178///
179/// When the handler cannot be installed.
180pub async fn ctrl_c() -> std::io::Result<()> {
181    #[cfg(unix)]
182    let mut listener = unix::signal(unix::SignalKind::interrupt())?;
183    #[cfg(windows)]
184    let mut listener = windows::ctrl_c()?;
185    listener.recv().await;
186    Ok(())
187}
188
189#[cfg(test)]
190mod tests;