1use 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 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)]
116struct 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#[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 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 2 => ctrl_c()
445 .expect("There was an issue creating ctrl+c event stream.")
446 .into(),
447 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}