// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::op2; use deno_core::AsyncRefCell; use deno_core::CancelFuture; use deno_core::CancelHandle; use deno_core::OpState; use deno_core::RcRef; use deno_core::Resource; use deno_core::ResourceId; use std::borrow::Cow; use std::cell::RefCell; #[cfg(unix)] use std::collections::BTreeMap; use std::rc::Rc; #[cfg(unix)] use std::sync::atomic::AtomicBool; #[cfg(unix)] use std::sync::Arc; #[cfg(unix)] use tokio::signal::unix::signal; #[cfg(unix)] use tokio::signal::unix::Signal; #[cfg(unix)] use tokio::signal::unix::SignalKind; #[cfg(windows)] use tokio::signal::windows::ctrl_break; #[cfg(windows)] use tokio::signal::windows::ctrl_c; #[cfg(windows)] use tokio::signal::windows::CtrlBreak; #[cfg(windows)] use tokio::signal::windows::CtrlC; deno_core::extension!( deno_signal, ops = [op_signal_bind, op_signal_unbind, op_signal_poll], state = |state| { #[cfg(unix)] { state.put(SignalState::default()); } } ); #[cfg(unix)] #[derive(Default)] struct SignalState { enable_default_handlers: BTreeMap>, } #[cfg(unix)] impl SignalState { /// Disable the default signal handler for the given signal. /// /// Returns the shared flag to enable the default handler later, and whether a default handler already existed. fn disable_default_handler( &mut self, signo: libc::c_int, ) -> (Arc, bool) { use std::collections::btree_map::Entry; match self.enable_default_handlers.entry(signo) { Entry::Occupied(entry) => { let enable = entry.get(); enable.store(false, std::sync::atomic::Ordering::Release); (enable.clone(), true) } Entry::Vacant(entry) => { let enable = Arc::new(AtomicBool::new(false)); entry.insert(enable.clone()); (enable, false) } } } } #[cfg(unix)] /// The resource for signal stream. /// The second element is the waker of polling future. struct SignalStreamResource { signal: AsyncRefCell, enable_default_handler: Arc, cancel: CancelHandle, } #[cfg(unix)] impl Resource for SignalStreamResource { fn name(&self) -> Cow { "signal".into() } fn close(self: Rc) { self.cancel.cancel(); } } // TODO: CtrlClose could be mapped to SIGHUP but that needs a // tokio::windows::signal::CtrlClose type, or something from a different crate #[cfg(windows)] enum WindowsSignal { Sigint(CtrlC), Sigbreak(CtrlBreak), } #[cfg(windows)] impl From for WindowsSignal { fn from(ctrl_c: CtrlC) -> Self { WindowsSignal::Sigint(ctrl_c) } } #[cfg(windows)] impl From for WindowsSignal { fn from(ctrl_break: CtrlBreak) -> Self { WindowsSignal::Sigbreak(ctrl_break) } } #[cfg(windows)] impl WindowsSignal { pub async fn recv(&mut self) -> Option<()> { match self { WindowsSignal::Sigint(ctrl_c) => ctrl_c.recv().await, WindowsSignal::Sigbreak(ctrl_break) => ctrl_break.recv().await, } } } #[cfg(windows)] struct SignalStreamResource { signal: AsyncRefCell, cancel: CancelHandle, } #[cfg(windows)] impl Resource for SignalStreamResource { fn name(&self) -> Cow { "signal".into() } fn close(self: Rc) { self.cancel.cancel(); } } #[cfg(target_os = "freebsd")] pub fn signal_str_to_int(s: &str) -> Result { match s { "SIGHUP" => Ok(1), "SIGINT" => Ok(2), "SIGQUIT" => Ok(3), "SIGILL" => Ok(4), "SIGTRAP" => Ok(5), "SIGIOT" => Ok(6), "SIGABRT" => Ok(6), "SIGEMT" => Ok(7), "SIGFPE" => Ok(8), "SIGKILL" => Ok(9), "SIGBUS" => Ok(10), "SIGSEGV" => Ok(11), "SIGSYS" => Ok(12), "SIGPIPE" => Ok(13), "SIGALRM" => Ok(14), "SIGTERM" => Ok(15), "SIGURG" => Ok(16), "SIGSTOP" => Ok(17), "SIGTSTP" => Ok(18), "SIGCONT" => Ok(19), "SIGCHLD" => Ok(20), "SIGTTIN" => Ok(21), "SIGTTOU" => Ok(22), "SIGIO" => Ok(23), "SIGXCPU" => Ok(24), "SIGXFSZ" => Ok(25), "SIGVTALRM" => Ok(26), "SIGPROF" => Ok(27), "SIGWINCH" => Ok(28), "SIGINFO" => Ok(29), "SIGUSR1" => Ok(30), "SIGUSR2" => Ok(31), "SIGTHR" => Ok(32), "SIGLIBRT" => Ok(33), _ => Err(type_error(format!("Invalid signal : {}", s))), } } #[cfg(target_os = "freebsd")] pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { match s { 1 => Ok("SIGHUP"), 2 => Ok("SIGINT"), 3 => Ok("SIGQUIT"), 4 => Ok("SIGILL"), 5 => Ok("SIGTRAP"), 6 => Ok("SIGABRT"), 7 => Ok("SIGEMT"), 8 => Ok("SIGFPE"), 9 => Ok("SIGKILL"), 10 => Ok("SIGBUS"), 11 => Ok("SIGSEGV"), 12 => Ok("SIGSYS"), 13 => Ok("SIGPIPE"), 14 => Ok("SIGALRM"), 15 => Ok("SIGTERM"), 16 => Ok("SIGURG"), 17 => Ok("SIGSTOP"), 18 => Ok("SIGTSTP"), 19 => Ok("SIGCONT"), 20 => Ok("SIGCHLD"), 21 => Ok("SIGTTIN"), 22 => Ok("SIGTTOU"), 23 => Ok("SIGIO"), 24 => Ok("SIGXCPU"), 25 => Ok("SIGXFSZ"), 26 => Ok("SIGVTALRM"), 27 => Ok("SIGPROF"), 28 => Ok("SIGWINCH"), 29 => Ok("SIGINFO"), 30 => Ok("SIGUSR1"), 31 => Ok("SIGUSR2"), 32 => Ok("SIGTHR"), 33 => Ok("SIGLIBRT"), _ => Err(type_error(format!("Invalid signal : {}", s))), } } #[cfg(target_os = "openbsd")] pub fn signal_str_to_int(s: &str) -> Result { match s { "SIGHUP" => Ok(1), "SIGINT" => Ok(2), "SIGQUIT" => Ok(3), "SIGILL" => Ok(4), "SIGTRAP" => Ok(5), "SIGIOT" => Ok(6), "SIGABRT" => Ok(6), "SIGEMT" => Ok(7), "SIGFPE" => Ok(8), "SIGKILL" => Ok(9), "SIGBUS" => Ok(10), "SIGSEGV" => Ok(11), "SIGSYS" => Ok(12), "SIGPIPE" => Ok(13), "SIGALRM" => Ok(14), "SIGTERM" => Ok(15), "SIGURG" => Ok(16), "SIGSTOP" => Ok(17), "SIGTSTP" => Ok(18), "SIGCONT" => Ok(19), "SIGCHLD" => Ok(20), "SIGTTIN" => Ok(21), "SIGTTOU" => Ok(22), "SIGIO" => Ok(23), "SIGXCPU" => Ok(24), "SIGXFSZ" => Ok(25), "SIGVTALRM" => Ok(26), "SIGPROF" => Ok(27), "SIGWINCH" => Ok(28), "SIGINFO" => Ok(29), "SIGUSR1" => Ok(30), "SIGUSR2" => Ok(31), "SIGTHR" => Ok(32), _ => Err(type_error(format!("Invalid signal : {}", s))), } } #[cfg(target_os = "openbsd")] pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { match s { 1 => Ok("SIGHUP"), 2 => Ok("SIGINT"), 3 => Ok("SIGQUIT"), 4 => Ok("SIGILL"), 5 => Ok("SIGTRAP"), 6 => Ok("SIGABRT"), 7 => Ok("SIGEMT"), 8 => Ok("SIGFPE"), 9 => Ok("SIGKILL"), 10 => Ok("SIGBUS"), 11 => Ok("SIGSEGV"), 12 => Ok("SIGSYS"), 13 => Ok("SIGPIPE"), 14 => Ok("SIGALRM"), 15 => Ok("SIGTERM"), 16 => Ok("SIGURG"), 17 => Ok("SIGSTOP"), 18 => Ok("SIGTSTP"), 19 => Ok("SIGCONT"), 20 => Ok("SIGCHLD"), 21 => Ok("SIGTTIN"), 22 => Ok("SIGTTOU"), 23 => Ok("SIGIO"), 24 => Ok("SIGXCPU"), 25 => Ok("SIGXFSZ"), 26 => Ok("SIGVTALRM"), 27 => Ok("SIGPROF"), 28 => Ok("SIGWINCH"), 29 => Ok("SIGINFO"), 30 => Ok("SIGUSR1"), 31 => Ok("SIGUSR2"), 32 => Ok("SIGTHR"), _ => Err(type_error(format!("Invalid signal : {}", s))), } } #[cfg(any(target_os = "android", target_os = "linux"))] pub fn signal_str_to_int(s: &str) -> Result { match s { "SIGHUP" => Ok(1), "SIGINT" => Ok(2), "SIGQUIT" => Ok(3), "SIGILL" => Ok(4), "SIGTRAP" => Ok(5), "SIGIOT" => Ok(6), "SIGABRT" => Ok(6), "SIGBUS" => Ok(7), "SIGFPE" => Ok(8), "SIGKILL" => Ok(9), "SIGUSR1" => Ok(10), "SIGSEGV" => Ok(11), "SIGUSR2" => Ok(12), "SIGPIPE" => Ok(13), "SIGALRM" => Ok(14), "SIGTERM" => Ok(15), "SIGSTKFLT" => Ok(16), "SIGCHLD" => Ok(17), "SIGCONT" => Ok(18), "SIGSTOP" => Ok(19), "SIGTSTP" => Ok(20), "SIGTTIN" => Ok(21), "SIGTTOU" => Ok(22), "SIGURG" => Ok(23), "SIGXCPU" => Ok(24), "SIGXFSZ" => Ok(25), "SIGVTALRM" => Ok(26), "SIGPROF" => Ok(27), "SIGWINCH" => Ok(28), "SIGIO" => Ok(29), "SIGPWR" => Ok(30), "SIGSYS" => Ok(31), _ => Err(type_error(format!("Invalid signal : {s}"))), } } #[cfg(any(target_os = "android", target_os = "linux"))] pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { match s { 1 => Ok("SIGHUP"), 2 => Ok("SIGINT"), 3 => Ok("SIGQUIT"), 4 => Ok("SIGILL"), 5 => Ok("SIGTRAP"), 6 => Ok("SIGABRT"), 7 => Ok("SIGBUS"), 8 => Ok("SIGFPE"), 9 => Ok("SIGKILL"), 10 => Ok("SIGUSR1"), 11 => Ok("SIGSEGV"), 12 => Ok("SIGUSR2"), 13 => Ok("SIGPIPE"), 14 => Ok("SIGALRM"), 15 => Ok("SIGTERM"), 16 => Ok("SIGSTKFLT"), 17 => Ok("SIGCHLD"), 18 => Ok("SIGCONT"), 19 => Ok("SIGSTOP"), 20 => Ok("SIGTSTP"), 21 => Ok("SIGTTIN"), 22 => Ok("SIGTTOU"), 23 => Ok("SIGURG"), 24 => Ok("SIGXCPU"), 25 => Ok("SIGXFSZ"), 26 => Ok("SIGVTALRM"), 27 => Ok("SIGPROF"), 28 => Ok("SIGWINCH"), 29 => Ok("SIGIO"), 30 => Ok("SIGPWR"), 31 => Ok("SIGSYS"), _ => Err(type_error(format!("Invalid signal : {s}"))), } } #[cfg(target_os = "macos")] pub fn signal_str_to_int(s: &str) -> Result { match s { "SIGHUP" => Ok(1), "SIGINT" => Ok(2), "SIGQUIT" => Ok(3), "SIGILL" => Ok(4), "SIGTRAP" => Ok(5), "SIGIOT" => Ok(6), "SIGABRT" => Ok(6), "SIGEMT" => Ok(7), "SIGFPE" => Ok(8), "SIGKILL" => Ok(9), "SIGBUS" => Ok(10), "SIGSEGV" => Ok(11), "SIGSYS" => Ok(12), "SIGPIPE" => Ok(13), "SIGALRM" => Ok(14), "SIGTERM" => Ok(15), "SIGURG" => Ok(16), "SIGSTOP" => Ok(17), "SIGTSTP" => Ok(18), "SIGCONT" => Ok(19), "SIGCHLD" => Ok(20), "SIGTTIN" => Ok(21), "SIGTTOU" => Ok(22), "SIGIO" => Ok(23), "SIGXCPU" => Ok(24), "SIGXFSZ" => Ok(25), "SIGVTALRM" => Ok(26), "SIGPROF" => Ok(27), "SIGWINCH" => Ok(28), "SIGINFO" => Ok(29), "SIGUSR1" => Ok(30), "SIGUSR2" => Ok(31), _ => Err(type_error(format!("Invalid signal: {s}"))), } } #[cfg(target_os = "macos")] pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { match s { 1 => Ok("SIGHUP"), 2 => Ok("SIGINT"), 3 => Ok("SIGQUIT"), 4 => Ok("SIGILL"), 5 => Ok("SIGTRAP"), 6 => Ok("SIGABRT"), 7 => Ok("SIGEMT"), 8 => Ok("SIGFPE"), 9 => Ok("SIGKILL"), 10 => Ok("SIGBUS"), 11 => Ok("SIGSEGV"), 12 => Ok("SIGSYS"), 13 => Ok("SIGPIPE"), 14 => Ok("SIGALRM"), 15 => Ok("SIGTERM"), 16 => Ok("SIGURG"), 17 => Ok("SIGSTOP"), 18 => Ok("SIGTSTP"), 19 => Ok("SIGCONT"), 20 => Ok("SIGCHLD"), 21 => Ok("SIGTTIN"), 22 => Ok("SIGTTOU"), 23 => Ok("SIGIO"), 24 => Ok("SIGXCPU"), 25 => Ok("SIGXFSZ"), 26 => Ok("SIGVTALRM"), 27 => Ok("SIGPROF"), 28 => Ok("SIGWINCH"), 29 => Ok("SIGINFO"), 30 => Ok("SIGUSR1"), 31 => Ok("SIGUSR2"), _ => Err(type_error(format!("Invalid signal: {s}"))), } } #[cfg(any(target_os = "solaris", target_os = "illumos"))] pub fn signal_str_to_int(s: &str) -> Result { match s { "SIGHUP" => Ok(1), "SIGINT" => Ok(2), "SIGQUIT" => Ok(3), "SIGILL" => Ok(4), "SIGTRAP" => Ok(5), "SIGIOT" => Ok(6), "SIGABRT" => Ok(6), "SIGEMT" => Ok(7), "SIGFPE" => Ok(8), "SIGKILL" => Ok(9), "SIGBUS" => Ok(10), "SIGSEGV" => Ok(11), "SIGSYS" => Ok(12), "SIGPIPE" => Ok(13), "SIGALRM" => Ok(14), "SIGTERM" => Ok(15), "SIGUSR1" => Ok(16), "SIGUSR2" => Ok(17), "SIGCLD" => Ok(18), "SIGCHLD" => Ok(18), "SIGPWR" => Ok(19), "SIGWINCH" => Ok(20), "SIGURG" => Ok(21), "SIGPOLL" => Ok(22), "SIGIO" => Ok(22), "SIGSTOP" => Ok(23), "SIGTSTP" => Ok(24), "SIGCONT" => Ok(25), "SIGTTIN" => Ok(26), "SIGTTOU" => Ok(27), "SIGVTALRM" => Ok(28), "SIGPROF" => Ok(29), "SIGXCPU" => Ok(30), "SIGXFSZ" => Ok(31), "SIGWAITING" => Ok(32), "SIGLWP" => Ok(33), "SIGFREEZE" => Ok(34), "SIGTHAW" => Ok(35), "SIGCANCEL" => Ok(36), "SIGLOST" => Ok(37), "SIGXRES" => Ok(38), "SIGJVM1" => Ok(39), "SIGJVM2" => Ok(40), _ => Err(type_error(format!("Invalid signal : {}", s))), } } #[cfg(any(target_os = "solaris", target_os = "illumos"))] pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { match s { 1 => Ok("SIGHUP"), 2 => Ok("SIGINT"), 3 => Ok("SIGQUIT"), 4 => Ok("SIGILL"), 5 => Ok("SIGTRAP"), 6 => Ok("SIGABRT"), 7 => Ok("SIGEMT"), 8 => Ok("SIGFPE"), 9 => Ok("SIGKILL"), 10 => Ok("SIGBUS"), 11 => Ok("SIGSEGV"), 12 => Ok("SIGSYS"), 13 => Ok("SIGPIPE"), 14 => Ok("SIGALRM"), 15 => Ok("SIGTERM"), 16 => Ok("SIGUSR1"), 17 => Ok("SIGUSR2"), 18 => Ok("SIGCHLD"), 19 => Ok("SIGPWR"), 20 => Ok("SIGWINCH"), 21 => Ok("SIGURG"), 22 => Ok("SIGPOLL"), 23 => Ok("SIGSTOP"), 24 => Ok("SIGTSTP"), 25 => Ok("SIGCONT"), 26 => Ok("SIGTTIN"), 27 => Ok("SIGTTOU"), 28 => Ok("SIGVTALRM"), 29 => Ok("SIGPROF"), 30 => Ok("SIGXCPU"), 31 => Ok("SIGXFSZ"), 32 => Ok("SIGWAITING"), 33 => Ok("SIGLWP"), 34 => Ok("SIGFREEZE"), 35 => Ok("SIGTHAW"), 36 => Ok("SIGCANCEL"), 37 => Ok("SIGLOST"), 38 => Ok("SIGXRES"), 39 => Ok("SIGJVM1"), 40 => Ok("SIGJVM2"), _ => Err(type_error(format!("Invalid signal : {}", s))), } } #[cfg(target_os = "windows")] pub fn signal_str_to_int(s: &str) -> Result { match s { "SIGINT" => Ok(2), "SIGBREAK" => Ok(21), _ => Err(type_error( "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK).", )), } } #[cfg(target_os = "windows")] pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { match s { 2 => Ok("SIGINT"), 21 => Ok("SIGBREAK"), _ => Err(type_error( "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK).", )), } } #[cfg(unix)] #[op2(fast)] #[smi] fn op_signal_bind( state: &mut OpState, #[string] sig: &str, ) -> Result { let signo = signal_str_to_int(sig)?; if signal_hook_registry::FORBIDDEN.contains(&signo) { return Err(type_error(format!( "Binding to signal '{sig}' is not allowed", ))); } let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?); let (enable_default_handler, has_default_handler) = state .borrow_mut::() .disable_default_handler(signo); let resource = SignalStreamResource { signal, cancel: Default::default(), enable_default_handler: enable_default_handler.clone(), }; let rid = state.resource_table.add(resource); if !has_default_handler { // restore default signal handler when the signal is unbound // this can error if the signal is not supported, if so let's just leave it as is let _ = signal_hook::flag::register_conditional_default( signo, enable_default_handler, ); } Ok(rid) } #[cfg(windows)] #[op2(fast)] #[smi] fn op_signal_bind( state: &mut OpState, #[string] sig: &str, ) -> Result { let signo = signal_str_to_int(sig)?; let resource = SignalStreamResource { signal: AsyncRefCell::new(match signo { // SIGINT 2 => ctrl_c() .expect("There was an issue creating ctrl+c event stream.") .into(), // SIGBREAK 21 => ctrl_break() .expect("There was an issue creating ctrl+break event stream.") .into(), _ => unimplemented!(), }), cancel: Default::default(), }; let rid = state.resource_table.add(resource); Ok(rid) } #[op2(async)] async fn op_signal_poll( state: Rc>, #[smi] rid: ResourceId, ) -> Result { let resource = state .borrow_mut() .resource_table .get::(rid)?; let cancel = RcRef::map(&resource, |r| &r.cancel); let mut signal = RcRef::map(&resource, |r| &r.signal).borrow_mut().await; match signal.recv().or_cancel(cancel).await { Ok(result) => Ok(result.is_none()), Err(_) => Ok(true), } } #[op2(fast)] pub fn op_signal_unbind( state: &mut OpState, #[smi] rid: ResourceId, ) -> Result<(), AnyError> { let resource = state.resource_table.take::(rid)?; #[cfg(unix)] { resource .enable_default_handler .store(true, std::sync::atomic::Ordering::Release); } resource.close(); Ok(()) }