deno_runtime/ops/
signal.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2use deno_core::op2;
3use deno_core::AsyncRefCell;
4use deno_core::CancelFuture;
5use deno_core::CancelHandle;
6use deno_core::OpState;
7use deno_core::RcRef;
8use deno_core::Resource;
9use deno_core::ResourceId;
10
11use std::borrow::Cow;
12use std::cell::RefCell;
13#[cfg(unix)]
14use std::collections::BTreeMap;
15use std::rc::Rc;
16#[cfg(unix)]
17use std::sync::atomic::AtomicBool;
18#[cfg(unix)]
19use std::sync::Arc;
20
21#[cfg(unix)]
22use tokio::signal::unix::signal;
23#[cfg(unix)]
24use tokio::signal::unix::Signal;
25#[cfg(unix)]
26use tokio::signal::unix::SignalKind;
27#[cfg(windows)]
28use tokio::signal::windows::ctrl_break;
29#[cfg(windows)]
30use tokio::signal::windows::ctrl_c;
31#[cfg(windows)]
32use tokio::signal::windows::CtrlBreak;
33#[cfg(windows)]
34use tokio::signal::windows::CtrlC;
35
36deno_core::extension!(
37  deno_signal,
38  ops = [op_signal_bind, op_signal_unbind, op_signal_poll],
39  state = |state| {
40    #[cfg(unix)]
41    {
42      state.put(SignalState::default());
43    }
44  }
45);
46
47#[derive(Debug, thiserror::Error)]
48pub enum SignalError {
49  #[cfg(any(
50    target_os = "android",
51    target_os = "linux",
52    target_os = "openbsd",
53    target_os = "openbsd",
54    target_os = "macos",
55    target_os = "solaris",
56    target_os = "illumos"
57  ))]
58  #[error("Invalid signal: {0}")]
59  InvalidSignalStr(String),
60  #[cfg(any(
61    target_os = "android",
62    target_os = "linux",
63    target_os = "openbsd",
64    target_os = "openbsd",
65    target_os = "macos",
66    target_os = "solaris",
67    target_os = "illumos"
68  ))]
69  #[error("Invalid signal: {0}")]
70  InvalidSignalInt(libc::c_int),
71  #[cfg(target_os = "windows")]
72  #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")]
73  InvalidSignalStr(String),
74  #[cfg(target_os = "windows")]
75  #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")]
76  InvalidSignalInt(libc::c_int),
77  #[error("Binding to signal '{0}' is not allowed")]
78  SignalNotAllowed(String),
79  #[error("{0}")]
80  Io(#[from] std::io::Error),
81}
82
83#[cfg(unix)]
84#[derive(Default)]
85struct SignalState {
86  enable_default_handlers: BTreeMap<libc::c_int, Arc<AtomicBool>>,
87}
88
89#[cfg(unix)]
90impl SignalState {
91  /// Disable the default signal handler for the given signal.
92  ///
93  /// Returns the shared flag to enable the default handler later, and whether a default handler already existed.
94  fn disable_default_handler(
95    &mut self,
96    signo: libc::c_int,
97  ) -> (Arc<AtomicBool>, bool) {
98    use std::collections::btree_map::Entry;
99
100    match self.enable_default_handlers.entry(signo) {
101      Entry::Occupied(entry) => {
102        let enable = entry.get();
103        enable.store(false, std::sync::atomic::Ordering::Release);
104        (enable.clone(), true)
105      }
106      Entry::Vacant(entry) => {
107        let enable = Arc::new(AtomicBool::new(false));
108        entry.insert(enable.clone());
109        (enable, false)
110      }
111    }
112  }
113}
114
115#[cfg(unix)]
116/// The resource for signal stream.
117/// The second element is the waker of polling future.
118struct SignalStreamResource {
119  signal: AsyncRefCell<Signal>,
120  enable_default_handler: Arc<AtomicBool>,
121  cancel: CancelHandle,
122}
123
124#[cfg(unix)]
125impl Resource for SignalStreamResource {
126  fn name(&self) -> Cow<str> {
127    "signal".into()
128  }
129
130  fn close(self: Rc<Self>) {
131    self.cancel.cancel();
132  }
133}
134
135// TODO: CtrlClose could be mapped to SIGHUP but that needs a
136// tokio::windows::signal::CtrlClose type, or something from a different crate
137#[cfg(windows)]
138enum WindowsSignal {
139  Sigint(CtrlC),
140  Sigbreak(CtrlBreak),
141}
142
143#[cfg(windows)]
144impl From<CtrlC> for WindowsSignal {
145  fn from(ctrl_c: CtrlC) -> Self {
146    WindowsSignal::Sigint(ctrl_c)
147  }
148}
149
150#[cfg(windows)]
151impl From<CtrlBreak> for WindowsSignal {
152  fn from(ctrl_break: CtrlBreak) -> Self {
153    WindowsSignal::Sigbreak(ctrl_break)
154  }
155}
156
157#[cfg(windows)]
158impl WindowsSignal {
159  pub async fn recv(&mut self) -> Option<()> {
160    match self {
161      WindowsSignal::Sigint(ctrl_c) => ctrl_c.recv().await,
162      WindowsSignal::Sigbreak(ctrl_break) => ctrl_break.recv().await,
163    }
164  }
165}
166
167#[cfg(windows)]
168struct SignalStreamResource {
169  signal: AsyncRefCell<WindowsSignal>,
170  cancel: CancelHandle,
171}
172
173#[cfg(windows)]
174impl Resource for SignalStreamResource {
175  fn name(&self) -> Cow<str> {
176    "signal".into()
177  }
178
179  fn close(self: Rc<Self>) {
180    self.cancel.cancel();
181  }
182}
183
184macro_rules! first_literal {
185  ($head:literal $(, $tail:literal)*) => {
186    $head
187  };
188}
189macro_rules! signal_dict {
190  ($(($number:literal, $($name:literal)|+)),*) => {
191    pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, SignalError> {
192      match s {
193        $($($name)|* => Ok($number),)*
194        _ => Err(SignalError::InvalidSignalStr(s.to_string())),
195      }
196    }
197
198    pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, SignalError> {
199      match s {
200        $($number => Ok(first_literal!($($name),+)),)*
201        _ => Err(SignalError::InvalidSignalInt(s)),
202      }
203    }
204  }
205}
206
207#[cfg(target_os = "freebsd")]
208signal_dict!(
209  (1, "SIGHUP"),
210  (2, "SIGINT"),
211  (3, "SIGQUIT"),
212  (4, "SIGILL"),
213  (5, "SIGTRAP"),
214  (6, "SIGABRT" | "SIGIOT"),
215  (7, "SIGEMT"),
216  (8, "SIGFPE"),
217  (9, "SIGKILL"),
218  (10, "SIGBUS"),
219  (11, "SIGSEGV"),
220  (12, "SIGSYS"),
221  (13, "SIGPIPE"),
222  (14, "SIGALRM"),
223  (15, "SIGTERM"),
224  (16, "SIGURG"),
225  (17, "SIGSTOP"),
226  (18, "SIGTSTP"),
227  (19, "SIGCONT"),
228  (20, "SIGCHLD"),
229  (21, "SIGTTIN"),
230  (22, "SIGTTOU"),
231  (23, "SIGIO"),
232  (24, "SIGXCPU"),
233  (25, "SIGXFSZ"),
234  (26, "SIGVTALRM"),
235  (27, "SIGPROF"),
236  (28, "SIGWINCH"),
237  (29, "SIGINFO"),
238  (30, "SIGUSR1"),
239  (31, "SIGUSR2"),
240  (32, "SIGTHR"),
241  (33, "SIGLIBRT")
242);
243
244#[cfg(target_os = "openbsd")]
245signal_dict!(
246  (1, "SIGHUP"),
247  (2, "SIGINT"),
248  (3, "SIGQUIT"),
249  (4, "SIGILL"),
250  (5, "SIGTRAP"),
251  (6, "SIGABRT" | "SIGIOT"),
252  (7, "SIGEMT"),
253  (8, "SIGKILL"),
254  (10, "SIGBUS"),
255  (11, "SIGSEGV"),
256  (12, "SIGSYS"),
257  (13, "SIGPIPE"),
258  (14, "SIGALRM"),
259  (15, "SIGTERM"),
260  (16, "SIGURG"),
261  (17, "SIGSTOP"),
262  (18, "SIGTSTP"),
263  (19, "SIGCONT"),
264  (20, "SIGCHLD"),
265  (21, "SIGTTIN"),
266  (22, "SIGTTOU"),
267  (23, "SIGIO"),
268  (24, "SIGXCPU"),
269  (25, "SIGXFSZ"),
270  (26, "SIGVTALRM"),
271  (27, "SIGPROF"),
272  (28, "SIGWINCH"),
273  (29, "SIGINFO"),
274  (30, "SIGUSR1"),
275  (31, "SIGUSR2"),
276  (32, "SIGTHR")
277);
278
279#[cfg(any(target_os = "android", target_os = "linux"))]
280signal_dict!(
281  (1, "SIGHUP"),
282  (2, "SIGINT"),
283  (3, "SIGQUIT"),
284  (4, "SIGILL"),
285  (5, "SIGTRAP"),
286  (6, "SIGABRT" | "SIGIOT"),
287  (7, "SIGBUS"),
288  (8, "SIGFPE"),
289  (9, "SIGKILL"),
290  (10, "SIGUSR1"),
291  (11, "SIGSEGV"),
292  (12, "SIGUSR2"),
293  (13, "SIGPIPE"),
294  (14, "SIGALRM"),
295  (15, "SIGTERM"),
296  (16, "SIGSTKFLT"),
297  (17, "SIGCHLD"),
298  (18, "SIGCONT"),
299  (19, "SIGSTOP"),
300  (20, "SIGTSTP"),
301  (21, "SIGTTIN"),
302  (22, "SIGTTOU"),
303  (23, "SIGURG"),
304  (24, "SIGXCPU"),
305  (25, "SIGXFSZ"),
306  (26, "SIGVTALRM"),
307  (27, "SIGPROF"),
308  (28, "SIGWINCH"),
309  (29, "SIGIO" | "SIGPOLL"),
310  (30, "SIGPWR"),
311  (31, "SIGSYS" | "SIGUNUSED")
312);
313
314#[cfg(target_os = "macos")]
315signal_dict!(
316  (1, "SIGHUP"),
317  (2, "SIGINT"),
318  (3, "SIGQUIT"),
319  (4, "SIGILL"),
320  (5, "SIGTRAP"),
321  (6, "SIGABRT" | "SIGIOT"),
322  (7, "SIGEMT"),
323  (8, "SIGFPE"),
324  (9, "SIGKILL"),
325  (10, "SIGBUS"),
326  (11, "SIGSEGV"),
327  (12, "SIGSYS"),
328  (13, "SIGPIPE"),
329  (14, "SIGALRM"),
330  (15, "SIGTERM"),
331  (16, "SIGURG"),
332  (17, "SIGSTOP"),
333  (18, "SIGTSTP"),
334  (19, "SIGCONT"),
335  (20, "SIGCHLD"),
336  (21, "SIGTTIN"),
337  (22, "SIGTTOU"),
338  (23, "SIGIO"),
339  (24, "SIGXCPU"),
340  (25, "SIGXFSZ"),
341  (26, "SIGVTALRM"),
342  (27, "SIGPROF"),
343  (28, "SIGWINCH"),
344  (29, "SIGINFO"),
345  (30, "SIGUSR1"),
346  (31, "SIGUSR2")
347);
348
349#[cfg(any(target_os = "solaris", target_os = "illumos"))]
350signal_dict!(
351  (1, "SIGHUP"),
352  (2, "SIGINT"),
353  (3, "SIGQUIT"),
354  (4, "SIGILL"),
355  (5, "SIGTRAP"),
356  (6, "SIGABRT" | "SIGIOT"),
357  (7, "SIGEMT"),
358  (8, "SIGFPE"),
359  (9, "SIGKILL"),
360  (10, "SIGBUS"),
361  (11, "SIGSEGV"),
362  (12, "SIGSYS"),
363  (13, "SIGPIPE"),
364  (14, "SIGALRM"),
365  (15, "SIGTERM"),
366  (16, "SIGUSR1"),
367  (17, "SIGUSR2"),
368  (18, "SIGCHLD"),
369  (19, "SIGPWR"),
370  (20, "SIGWINCH"),
371  (21, "SIGURG"),
372  (22, "SIGPOLL"),
373  (23, "SIGSTOP"),
374  (24, "SIGTSTP"),
375  (25, "SIGCONT"),
376  (26, "SIGTTIN"),
377  (27, "SIGTTOU"),
378  (28, "SIGVTALRM"),
379  (29, "SIGPROF"),
380  (30, "SIGXCPU"),
381  (31, "SIGXFSZ"),
382  (32, "SIGWAITING"),
383  (33, "SIGLWP"),
384  (34, "SIGFREEZE"),
385  (35, "SIGTHAW"),
386  (36, "SIGCANCEL"),
387  (37, "SIGLOST"),
388  (38, "SIGXRES"),
389  (39, "SIGJVM1"),
390  (40, "SIGJVM2")
391);
392
393#[cfg(target_os = "windows")]
394signal_dict!((2, "SIGINT"), (21, "SIGBREAK"));
395
396#[cfg(unix)]
397#[op2(fast)]
398#[smi]
399fn op_signal_bind(
400  state: &mut OpState,
401  #[string] sig: &str,
402) -> Result<ResourceId, SignalError> {
403  let signo = signal_str_to_int(sig)?;
404  if signal_hook_registry::FORBIDDEN.contains(&signo) {
405    return Err(SignalError::SignalNotAllowed(sig.to_string()));
406  }
407
408  let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?);
409
410  let (enable_default_handler, has_default_handler) = state
411    .borrow_mut::<SignalState>()
412    .disable_default_handler(signo);
413
414  let resource = SignalStreamResource {
415    signal,
416    cancel: Default::default(),
417    enable_default_handler: enable_default_handler.clone(),
418  };
419  let rid = state.resource_table.add(resource);
420
421  if !has_default_handler {
422    // restore default signal handler when the signal is unbound
423    // this can error if the signal is not supported, if so let's just leave it as is
424    let _ = signal_hook::flag::register_conditional_default(
425      signo,
426      enable_default_handler,
427    );
428  }
429
430  Ok(rid)
431}
432
433#[cfg(windows)]
434#[op2(fast)]
435#[smi]
436fn op_signal_bind(
437  state: &mut OpState,
438  #[string] sig: &str,
439) -> Result<ResourceId, SignalError> {
440  let signo = signal_str_to_int(sig)?;
441  let resource = SignalStreamResource {
442    signal: AsyncRefCell::new(match signo {
443      // SIGINT
444      2 => ctrl_c()
445        .expect("There was an issue creating ctrl+c event stream.")
446        .into(),
447      // SIGBREAK
448      21 => ctrl_break()
449        .expect("There was an issue creating ctrl+break event stream.")
450        .into(),
451      _ => unimplemented!(),
452    }),
453    cancel: Default::default(),
454  };
455  let rid = state.resource_table.add(resource);
456  Ok(rid)
457}
458
459#[op2(async)]
460async fn op_signal_poll(
461  state: Rc<RefCell<OpState>>,
462  #[smi] rid: ResourceId,
463) -> Result<bool, deno_core::error::AnyError> {
464  let resource = state
465    .borrow_mut()
466    .resource_table
467    .get::<SignalStreamResource>(rid)?;
468
469  let cancel = RcRef::map(&resource, |r| &r.cancel);
470  let mut signal = RcRef::map(&resource, |r| &r.signal).borrow_mut().await;
471
472  match signal.recv().or_cancel(cancel).await {
473    Ok(result) => Ok(result.is_none()),
474    Err(_) => Ok(true),
475  }
476}
477
478#[op2(fast)]
479pub fn op_signal_unbind(
480  state: &mut OpState,
481  #[smi] rid: ResourceId,
482) -> Result<(), deno_core::error::AnyError> {
483  let resource = state.resource_table.take::<SignalStreamResource>(rid)?;
484
485  #[cfg(unix)]
486  {
487    resource
488      .enable_default_handler
489      .store(true, std::sync::atomic::Ordering::Release);
490  }
491
492  resource.close();
493  Ok(())
494}