Skip to main content

deno_task_shell/shell/
types.rs

1// Copyright 2018-2025 the Deno authors. MIT license.
2
3use std::borrow::Cow;
4use std::cell::Cell;
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::ffi::OsStr;
8use std::ffi::OsString;
9use std::io::Read;
10use std::io::Write;
11use std::path::Path;
12use std::path::PathBuf;
13use std::rc::Rc;
14use std::rc::Weak;
15
16use anyhow::Result;
17use bitflags::bitflags;
18use futures::future::LocalBoxFuture;
19use tokio::sync::broadcast;
20use tokio::task::JoinHandle;
21
22use crate::shell::child_process_tracker::ChildProcessTracker;
23
24use super::commands::ShellCommand;
25use super::commands::builtin_commands;
26
27bitflags! {
28  /// Shell options that can be set via `shopt` or `set -o`.
29  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
30  pub struct ShellOptions: u32 {
31    /// When set, a glob pattern that matches no files expands to nothing
32    /// (empty) rather than returning an error.
33    const NULLGLOB = 1 << 0;
34    /// When set, a glob pattern that matches no files causes an error.
35    /// When unset, unmatched globs are passed through literally (bash default).
36    const FAILGLOB = 1 << 1;
37    /// When set, pipeline exit code is the rightmost non-zero exit code.
38    /// Set via `set -o pipefail`.
39    const PIPEFAIL = 1 << 2;
40    /// When set, the pattern `**` used in a pathname expansion context will
41    /// match all files and zero or more directories and subdirectories.
42    const GLOBSTAR = 1 << 3;
43  }
44}
45
46impl Default for ShellOptions {
47  fn default() -> Self {
48    ShellOptions::GLOBSTAR
49  }
50}
51
52/// Exit code set when an async task fails or the main execution
53/// line fail.
54#[derive(Debug, Default, Clone)]
55pub(crate) struct TreeExitCodeCell(Rc<Cell<i32>>);
56
57impl TreeExitCodeCell {
58  pub fn try_set(&self, exit_code: i32) {
59    if self.0.get() == 0 {
60      // only set it for the first non-zero failure
61      self.0.set(exit_code);
62    }
63  }
64
65  pub fn get(&self) -> Option<i32> {
66    match self.0.get() {
67      0 => None,
68      code => Some(code),
69    }
70  }
71}
72
73#[derive(Clone)]
74pub struct ShellState {
75  /// Environment variables that should be passed down to sub commands
76  /// and used when evaluating environment variables.
77  env_vars: HashMap<OsString, OsString>,
78  /// Variables that should be evaluated within the shell and
79  /// not passed down to any sub commands.
80  shell_vars: HashMap<OsString, OsString>,
81  cwd: PathBuf,
82  commands: Rc<HashMap<String, Rc<dyn ShellCommand>>>,
83  kill_signal: KillSignal,
84  process_tracker: ChildProcessTracker,
85  tree_exit_code_cell: TreeExitCodeCell,
86  /// Shell options set via `shopt` or `set -o`.
87  shell_options: ShellOptions,
88}
89
90impl ShellState {
91  pub fn new(
92    env_vars: HashMap<OsString, OsString>,
93    cwd: PathBuf,
94    custom_commands: HashMap<String, Rc<dyn ShellCommand>>,
95    kill_signal: KillSignal,
96  ) -> Self {
97    assert!(cwd.is_absolute());
98    let mut commands = builtin_commands();
99    commands.extend(custom_commands);
100    let mut result = Self {
101      env_vars: Default::default(),
102      shell_vars: Default::default(),
103      cwd: PathBuf::new(),
104      commands: Rc::new(commands),
105      kill_signal,
106      process_tracker: ChildProcessTracker::new(),
107      tree_exit_code_cell: Default::default(),
108      shell_options: ShellOptions::default(),
109    };
110    // ensure the data is normalized
111    for (name, value) in env_vars {
112      result.apply_env_var(&name, &value);
113    }
114    result.set_cwd(cwd);
115    result
116  }
117
118  pub fn cwd(&self) -> &PathBuf {
119    &self.cwd
120  }
121
122  pub fn env_vars(&self) -> &HashMap<OsString, OsString> {
123    &self.env_vars
124  }
125
126  pub fn get_var(&self, name: &OsStr) -> Option<&OsString> {
127    let name = if cfg!(windows) {
128      Cow::Owned(name.to_ascii_uppercase())
129    } else {
130      Cow::Borrowed(name)
131    };
132    let name: &OsStr = &name;
133    self
134      .env_vars
135      .get(name)
136      .or_else(|| self.shell_vars.get(name))
137  }
138
139  pub fn set_cwd(&mut self, cwd: PathBuf) {
140    self.cwd = cwd.clone();
141    // $PWD holds the current working directory, so we keep cwd and $PWD in sync
142    self.env_vars.insert("PWD".into(), cwd.into_os_string());
143  }
144
145  pub fn apply_changes(&mut self, changes: &[EnvChange]) {
146    for change in changes {
147      self.apply_change(change);
148    }
149  }
150
151  pub fn apply_change(&mut self, change: &EnvChange) {
152    match change {
153      EnvChange::SetEnvVar(name, value) => self.apply_env_var(name, value),
154      EnvChange::SetShellVar(name, value) => {
155        if self.env_vars.contains_key(name) {
156          self.apply_env_var(name, value);
157        } else {
158          self
159            .shell_vars
160            .insert(name.to_os_string(), value.to_os_string());
161        }
162      }
163      EnvChange::UnsetVar(name) => {
164        self.shell_vars.remove(name);
165        self.env_vars.remove(name);
166      }
167      EnvChange::Cd(new_dir) => {
168        self.set_cwd(new_dir.clone());
169      }
170      EnvChange::SetOption(option, enabled) => {
171        self.set_shell_option(*option, *enabled);
172      }
173    }
174  }
175
176  pub fn apply_env_var(&mut self, name: &OsStr, value: &OsStr) {
177    let name = if cfg!(windows) {
178      // environment variables are case insensitive on windows
179      name.to_ascii_uppercase()
180    } else {
181      name.to_os_string()
182    };
183    if name == "PWD" {
184      let cwd = Path::new(value);
185      if cwd.is_absolute()
186        && let Ok(cwd) = deno_path_util::fs::canonicalize_path_maybe_not_exists(
187          &sys_traits::impls::RealSys,
188          cwd,
189        )
190      {
191        // this will update the environment variable too
192        self.set_cwd(cwd);
193      }
194    } else {
195      self.shell_vars.remove(&name);
196      self.env_vars.insert(name, value.to_os_string());
197    }
198  }
199
200  pub fn kill_signal(&self) -> &KillSignal {
201    &self.kill_signal
202  }
203
204  pub fn shell_options(&self) -> ShellOptions {
205    self.shell_options
206  }
207
208  pub fn set_shell_option(&mut self, option: ShellOptions, enabled: bool) {
209    if enabled {
210      self.shell_options.insert(option);
211    } else {
212      self.shell_options.remove(option);
213    }
214  }
215
216  pub fn track_child_process(&self, child: &tokio::process::Child) {
217    self.process_tracker.track(child);
218  }
219
220  pub(crate) fn tree_exit_code_cell(&self) -> &TreeExitCodeCell {
221    &self.tree_exit_code_cell
222  }
223
224  /// Resolves a custom command that was injected.
225  pub fn resolve_custom_command(
226    &self,
227    name: &OsStr,
228  ) -> Option<Rc<dyn ShellCommand>> {
229    // only bother supporting utf8 custom command names for now
230    name
231      .to_str()
232      // uses an Rc to allow resolving a command without borrowing from self
233      .and_then(|name| self.commands.get(name).cloned())
234  }
235
236  /// Resolves the path to a command from the current working directory.
237  ///
238  /// Does not take injected custom commands into account.
239  pub fn resolve_command_path(
240    &self,
241    command_name: &OsStr,
242  ) -> Result<PathBuf, super::which::CommandPathResolutionError> {
243    super::which::resolve_command_path(command_name, self.cwd(), self)
244  }
245
246  pub fn with_child_signal(&self) -> ShellState {
247    let mut state = self.clone();
248    state.kill_signal = self.kill_signal.child_signal();
249    state.tree_exit_code_cell = TreeExitCodeCell::default();
250    state
251  }
252}
253
254impl sys_traits::BaseEnvVar for ShellState {
255  fn base_env_var_os(&self, key: &OsStr) -> Option<OsString> {
256    self.env_vars.get(key).cloned()
257  }
258}
259
260#[derive(Debug, Clone, PartialEq, Eq)]
261pub enum EnvChange {
262  // `export ENV_VAR=VALUE`
263  SetEnvVar(OsString, OsString),
264  // `ENV_VAR=VALUE`
265  SetShellVar(OsString, OsString),
266  // `unset ENV_VAR`
267  UnsetVar(OsString),
268  Cd(PathBuf),
269  // `shopt -s/-u option` or `set -o option`
270  SetOption(ShellOptions, bool),
271}
272
273pub type FutureExecuteResult = LocalBoxFuture<'static, ExecuteResult>;
274
275#[derive(Debug)]
276pub enum ExecuteResult {
277  Exit(i32, Vec<JoinHandle<i32>>),
278  Continue(i32, Vec<EnvChange>, Vec<JoinHandle<i32>>),
279}
280
281impl ExecuteResult {
282  pub fn from_exit_code(exit_code: i32) -> ExecuteResult {
283    ExecuteResult::Continue(exit_code, Vec::new(), Vec::new())
284  }
285
286  pub fn into_exit_code_and_handles(self) -> (i32, Vec<JoinHandle<i32>>) {
287    match self {
288      ExecuteResult::Exit(code, handles) => (code, handles),
289      ExecuteResult::Continue(code, _, handles) => (code, handles),
290    }
291  }
292
293  pub fn into_handles(self) -> Vec<JoinHandle<i32>> {
294    self.into_exit_code_and_handles().1
295  }
296}
297
298/// Reader side of a pipe.
299#[derive(Debug)]
300pub enum ShellPipeReader {
301  OsPipe(std::io::PipeReader),
302  StdFile(std::fs::File),
303}
304
305impl Clone for ShellPipeReader {
306  fn clone(&self) -> Self {
307    match self {
308      Self::OsPipe(pipe) => Self::OsPipe(pipe.try_clone().unwrap()),
309      Self::StdFile(file) => Self::StdFile(file.try_clone().unwrap()),
310    }
311  }
312}
313
314impl ShellPipeReader {
315  pub fn stdin() -> ShellPipeReader {
316    #[cfg(unix)]
317    pub fn dup_stdin_as_pipe_reader() -> std::io::PipeReader {
318      use std::os::fd::AsFd;
319      use std::os::fd::FromRawFd;
320      use std::os::fd::IntoRawFd;
321      let owned = std::io::stdin().as_fd().try_clone_to_owned().unwrap();
322      let raw = owned.into_raw_fd();
323      // SAFETY: `raw` is a fresh, owned fd; PipeReader will close it.
324      unsafe { std::io::PipeReader::from_raw_fd(raw) }
325    }
326
327    #[cfg(windows)]
328    pub fn dup_stdin_as_pipe_reader() -> std::io::PipeReader {
329      use std::os::windows::io::AsHandle;
330      use std::os::windows::io::FromRawHandle;
331      use std::os::windows::io::IntoRawHandle;
332      let owned = std::io::stdin().as_handle().try_clone_to_owned().unwrap();
333      let raw = owned.into_raw_handle();
334      // SAFETY: `raw` is a fresh, owned HANDLE; PipeReader will close it.
335      unsafe { std::io::PipeReader::from_raw_handle(raw) }
336    }
337
338    ShellPipeReader::OsPipe(dup_stdin_as_pipe_reader())
339  }
340
341  pub fn from_raw(reader: std::io::PipeReader) -> Self {
342    Self::OsPipe(reader)
343  }
344
345  pub fn from_std(std_file: std::fs::File) -> Self {
346    Self::StdFile(std_file)
347  }
348
349  #[cfg(test)]
350  #[allow(clippy::should_implement_trait)]
351  pub fn from_str(data: &str) -> Self {
352    use std::io::Write;
353    let (read, mut write) = std::io::pipe().unwrap();
354    write.write_all(data.as_bytes()).unwrap();
355    Self::OsPipe(read)
356  }
357
358  pub fn into_stdio(self) -> std::process::Stdio {
359    match self {
360      Self::OsPipe(pipe) => pipe.into(),
361      Self::StdFile(file) => file.into(),
362    }
363  }
364
365  /// Pipe everything to the specified writer
366  pub fn pipe_to(self, writer: &mut dyn Write) -> Result<()> {
367    // don't bother flushing here because this won't ever be called
368    // with a Rust wrapped stdout/stderr
369    self.pipe_to_inner(writer, false)
370  }
371
372  fn pipe_to_with_flushing(self, writer: &mut dyn Write) -> Result<()> {
373    self.pipe_to_inner(writer, true)
374  }
375
376  fn pipe_to_inner(
377    mut self,
378    writer: &mut dyn Write,
379    flush: bool,
380  ) -> Result<()> {
381    loop {
382      let mut buffer = [0; 512]; // todo: what is an appropriate buffer size?
383      let size = match &mut self {
384        ShellPipeReader::OsPipe(pipe) => pipe.read(&mut buffer)?,
385        ShellPipeReader::StdFile(file) => file.read(&mut buffer)?,
386      };
387      if size == 0 {
388        break;
389      }
390      writer.write_all(&buffer[0..size])?;
391      if flush {
392        writer.flush()?;
393      }
394    }
395    Ok(())
396  }
397
398  /// Pipes this pipe to the specified sender.
399  pub fn pipe_to_sender(self, mut sender: ShellPipeWriter) -> Result<()> {
400    match &mut sender {
401      ShellPipeWriter::OsPipe(pipe) => self.pipe_to(pipe),
402      ShellPipeWriter::StdFile(file) => self.pipe_to(file),
403      // Don't lock stdout/stderr here because we want to release the lock
404      // when reading from the sending pipe. Additionally, we want
405      // to flush after every write because Rust's wrapper has an
406      // internal buffer and Deno doesn't buffer stdout/stderr.
407      ShellPipeWriter::Stdout => {
408        self.pipe_to_with_flushing(&mut std::io::stdout())
409      }
410      ShellPipeWriter::Stderr => {
411        self.pipe_to_with_flushing(&mut std::io::stderr())
412      }
413      ShellPipeWriter::Null => Ok(()),
414    }
415  }
416
417  /// Pipes the reader to a string handle that is resolved when the pipe's
418  /// writer is closed.
419  pub fn pipe_to_string_handle(self) -> JoinHandle<String> {
420    tokio::task::spawn_blocking(|| {
421      let mut buf = Vec::new();
422      self.pipe_to(&mut buf).unwrap();
423      String::from_utf8_lossy(&buf).to_string()
424    })
425  }
426
427  pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
428    match self {
429      ShellPipeReader::OsPipe(pipe) => pipe.read(buf).map_err(|e| e.into()),
430      ShellPipeReader::StdFile(file) => file.read(buf).map_err(|e| e.into()),
431    }
432  }
433}
434
435/// Writer side of a pipe.
436///
437/// Ensure that all of these are dropped when complete in order to
438/// prevent deadlocks where the reader hangs waiting for a read.
439#[derive(Debug)]
440pub enum ShellPipeWriter {
441  OsPipe(std::io::PipeWriter),
442  StdFile(std::fs::File),
443  // For stdout and stderr, instead of directly duplicating the raw pipes
444  // and putting them in a ShellPipeWriter::OsPipe(...), we use Rust std's
445  // stdout() and stderr() wrappers because it contains some code to solve
446  // some encoding issues on Windows (ex. emojis). For more details, see
447  // library/std/src/sys/windows/stdio.rs in Rust's source code.
448  Stdout,
449  Stderr,
450  Null,
451}
452
453impl Clone for ShellPipeWriter {
454  fn clone(&self) -> Self {
455    match self {
456      Self::OsPipe(pipe) => Self::OsPipe(pipe.try_clone().unwrap()),
457      Self::StdFile(file) => Self::StdFile(file.try_clone().unwrap()),
458      Self::Stdout => Self::Stdout,
459      Self::Stderr => Self::Stderr,
460      Self::Null => Self::Null,
461    }
462  }
463}
464
465impl ShellPipeWriter {
466  pub fn stdout() -> Self {
467    Self::Stdout
468  }
469
470  pub fn stderr() -> Self {
471    Self::Stderr
472  }
473
474  pub fn null() -> Self {
475    Self::Null
476  }
477
478  pub fn from_std(std_file: std::fs::File) -> Self {
479    Self::StdFile(std_file)
480  }
481
482  pub fn into_stdio(self) -> std::process::Stdio {
483    match self {
484      Self::OsPipe(pipe) => pipe.into(),
485      Self::StdFile(file) => file.into(),
486      Self::Stdout => std::process::Stdio::inherit(),
487      Self::Stderr => std::process::Stdio::inherit(),
488      Self::Null => std::process::Stdio::null(),
489    }
490  }
491
492  pub fn write_all(&mut self, bytes: &[u8]) -> Result<()> {
493    self.write_all_iter(std::iter::once(bytes))
494  }
495
496  pub fn write_all_iter<'a>(
497    &mut self,
498    iter: impl Iterator<Item = &'a [u8]> + 'a,
499  ) -> Result<()> {
500    match self {
501      Self::OsPipe(pipe) => {
502        for bytes in iter {
503          pipe.write_all(bytes)?;
504        }
505      }
506      Self::StdFile(file) => {
507        for bytes in iter {
508          file.write_all(bytes)?
509        }
510      }
511      // For both stdout & stderr, we want to flush after each
512      // write in order to bypass Rust's internal buffer.
513      Self::Stdout => {
514        let mut stdout = std::io::stdout().lock();
515        for bytes in iter {
516          stdout.write_all(bytes)?;
517        }
518        stdout.flush()?;
519      }
520      Self::Stderr => {
521        let mut stderr = std::io::stderr().lock();
522        for bytes in iter {
523          stderr.write_all(bytes)?;
524        }
525        stderr.flush()?;
526      }
527      Self::Null => {}
528    }
529    Ok(())
530  }
531
532  pub fn write_line(&mut self, line: &str) -> Result<()> {
533    let bytes = format!("{line}\n");
534    self.write_all(bytes.as_bytes())
535  }
536}
537
538/// Used to communicate between commands.
539pub fn pipe() -> (ShellPipeReader, ShellPipeWriter) {
540  let (reader, writer) = std::io::pipe().unwrap();
541  (
542    ShellPipeReader::OsPipe(reader),
543    ShellPipeWriter::OsPipe(writer),
544  )
545}
546
547#[derive(Debug)]
548struct KillSignalInner {
549  // WARNING: This should struct should not be made Sync.
550  // Some of the code in this project depends on this not
551  // being cancelled at any time by another thread. For example,
552  // the abort code is checked before executing a sub process and
553  // then awaited after. If an abort happened between that then
554  // it could be missed.
555  aborted_code: RefCell<Option<i32>>,
556  sender: broadcast::Sender<SignalKind>,
557  children: RefCell<Vec<Weak<KillSignalInner>>>,
558}
559
560impl KillSignalInner {
561  pub fn send(&self, signal_kind: SignalKind) {
562    if signal_kind.causes_abort() {
563      let mut stored_aborted_code = self.aborted_code.borrow_mut();
564      if stored_aborted_code.is_none() {
565        *stored_aborted_code = Some(signal_kind.aborted_code());
566      }
567    }
568    _ = self.sender.send(signal_kind);
569
570    // notify children
571    self.children.borrow_mut().retain(|weak_child| {
572      if let Some(child) = weak_child.upgrade() {
573        child.send(signal_kind);
574        true
575      } else {
576        false // clean-up dropped children
577      }
578    });
579  }
580}
581
582/// Used to send signals to commands.
583#[derive(Debug, Clone)]
584pub struct KillSignal(Rc<KillSignalInner>);
585
586impl Default for KillSignal {
587  fn default() -> Self {
588    let (sender, _) = broadcast::channel(100);
589    Self(Rc::new(KillSignalInner {
590      aborted_code: RefCell::new(None),
591      sender,
592      children: Default::default(),
593    }))
594  }
595}
596
597impl KillSignal {
598  /// Exit code to use when aborted.
599  pub fn aborted_code(&self) -> Option<i32> {
600    *self.0.aborted_code.borrow()
601  }
602
603  /// Creates a signal that will only send signals to itself
604  /// and all descendants--not the parent signal.
605  pub fn child_signal(&self) -> Self {
606    let (sender, _) = broadcast::channel(100);
607    let child = Rc::new(KillSignalInner {
608      aborted_code: RefCell::new(self.aborted_code()),
609      sender,
610      children: RefCell::new(Vec::new()),
611    });
612
613    // Add the child to the parent's list of children
614    self.0.children.borrow_mut().push(Rc::downgrade(&child));
615
616    Self(child)
617  }
618
619  /// Creates a `DropKillSignalGuard` that will send a `SignalKind::SIGTERM` on drop.
620  pub fn drop_guard(self) -> KillSignalDropGuard {
621    self.drop_guard_with_kind(SignalKind::SIGTERM)
622  }
623
624  /// Creates a `DropKillSignalGuard` that will send the specified signal on drop.
625  pub fn drop_guard_with_kind(self, kind: SignalKind) -> KillSignalDropGuard {
626    KillSignalDropGuard {
627      disarmed: Cell::new(false),
628      kill_signal_kind: kind,
629      signal: self,
630    }
631  }
632
633  /// Send a signal to commands being run.
634  pub fn send(&self, signal: SignalKind) {
635    self.0.send(signal)
636  }
637
638  /// Waits for only signals deemed to abort a command.
639  pub async fn wait_aborted(&self) -> SignalKind {
640    let mut receiver = self.0.sender.subscribe();
641    loop {
642      // unwrap is ok because we're holding a sender in `self`
643      let signal = receiver.recv().await.unwrap();
644      if signal.causes_abort() {
645        return signal;
646      }
647    }
648  }
649
650  /// Waits for any signal to be received.
651  pub async fn wait_any(&self) -> SignalKind {
652    let mut receiver = self.0.sender.subscribe();
653    // unwrap is ok because we're holding a sender in `self`
654    receiver.recv().await.unwrap()
655  }
656}
657
658/// Guard that on drop will send a signal on the associated `KillSignal`.
659#[derive(Debug)]
660pub struct KillSignalDropGuard {
661  disarmed: Cell<bool>,
662  kill_signal_kind: SignalKind,
663  signal: KillSignal,
664}
665
666impl Drop for KillSignalDropGuard {
667  fn drop(&mut self) {
668    if !self.disarmed.get() {
669      self.signal.send(self.kill_signal_kind);
670    }
671  }
672}
673
674impl KillSignalDropGuard {
675  /// Prevent the drop guard from sending a signal on drop.
676  pub fn disarm(&self) {
677    self.disarmed.set(true);
678  }
679}
680
681#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
682pub enum SignalKind {
683  SIGTERM,
684  SIGKILL,
685  SIGABRT,
686  SIGQUIT,
687  SIGINT,
688  SIGSTOP,
689  Other(i32),
690}
691
692impl SignalKind {
693  pub fn causes_abort(&self) -> bool {
694    match self {
695      SignalKind::SIGTERM
696      | SignalKind::SIGKILL
697      | SignalKind::SIGQUIT
698      | SignalKind::SIGINT
699      | SignalKind::SIGSTOP
700      // does this make sense?
701      | SignalKind::SIGABRT => true,
702      SignalKind::Other(_) => false,
703    }
704  }
705
706  pub fn aborted_code(&self) -> i32 {
707    let value: i32 = (*self).into();
708    128 + value
709  }
710}
711
712impl From<i32> for SignalKind {
713  fn from(value: i32) -> Self {
714    #[cfg(unix)]
715    match value {
716      nix::libc::SIGINT => SignalKind::SIGINT,
717      nix::libc::SIGQUIT => SignalKind::SIGQUIT,
718      nix::libc::SIGABRT => SignalKind::SIGABRT,
719      nix::libc::SIGKILL => SignalKind::SIGKILL,
720      nix::libc::SIGTERM => SignalKind::SIGTERM,
721      nix::libc::SIGSTOP => SignalKind::SIGSTOP,
722      _ => SignalKind::Other(value),
723    }
724    #[cfg(not(unix))]
725    match value {
726      2 => SignalKind::SIGINT,
727      3 => SignalKind::SIGQUIT,
728      6 => SignalKind::SIGABRT,
729      9 => SignalKind::SIGKILL,
730      15 => SignalKind::SIGTERM,
731      19 => SignalKind::SIGSTOP,
732      _ => SignalKind::Other(value),
733    }
734  }
735}
736
737impl From<SignalKind> for i32 {
738  fn from(kind: SignalKind) -> i32 {
739    #[cfg(unix)]
740    match kind {
741      SignalKind::SIGINT => nix::libc::SIGINT,
742      SignalKind::SIGQUIT => nix::libc::SIGQUIT,
743      SignalKind::SIGABRT => nix::libc::SIGABRT,
744      SignalKind::SIGKILL => nix::libc::SIGKILL,
745      SignalKind::SIGTERM => nix::libc::SIGTERM,
746      SignalKind::SIGSTOP => nix::libc::SIGSTOP,
747      SignalKind::Other(value) => value,
748    }
749    #[cfg(not(unix))]
750    match kind {
751      SignalKind::SIGINT => 2,
752      SignalKind::SIGQUIT => 3,
753      SignalKind::SIGABRT => 6,
754      SignalKind::SIGKILL => 9,
755      SignalKind::SIGTERM => 15,
756      SignalKind::SIGSTOP => 19,
757      SignalKind::Other(value) => value,
758    }
759  }
760}
761
762#[cfg(test)]
763mod test {
764  use crate::KillSignal;
765  use crate::SignalKind;
766
767  #[tokio::test]
768  async fn test_send_and_wait_any() {
769    let kill_signal = KillSignal::default();
770
771    // Spawn a task to send a signal
772    let signal_sender = kill_signal.clone();
773    deno_unsync::spawn(async move {
774      signal_sender.send(SignalKind::SIGTERM);
775    });
776
777    // Wait for the signal in the main task
778    let signal = kill_signal.wait_any().await;
779    assert_eq!(signal, SignalKind::SIGTERM);
780  }
781
782  #[tokio::test]
783  async fn test_signal_propagation_to_child_and_grandchild() {
784    let parent_signal = KillSignal::default();
785    let child_signal = parent_signal.child_signal();
786    let sibling_signal = parent_signal.child_signal();
787    let grandchild_signal = child_signal.child_signal();
788
789    // Spawn a task to send a signal from the parent
790    let parent = parent_signal.clone();
791    deno_unsync::spawn(async move {
792      parent.send(SignalKind::SIGKILL);
793    });
794
795    let signals = futures::join!(
796      child_signal.wait_any(),
797      sibling_signal.wait_any(),
798      grandchild_signal.wait_any()
799    );
800
801    for signal in [signals.0, signals.1, signals.2].into_iter() {
802      assert_eq!(signal, SignalKind::SIGKILL);
803    }
804    assert_eq!(child_signal.aborted_code(), Some(128 + 9));
805    assert_eq!(sibling_signal.aborted_code(), Some(128 + 9));
806    assert_eq!(grandchild_signal.aborted_code(), Some(128 + 9));
807  }
808
809  #[tokio::test]
810  async fn test_signal_propagation_on_sub_tree() {
811    let parent_signal = KillSignal::default();
812    let child_signal = parent_signal.child_signal();
813    let sibling_signal = parent_signal.child_signal();
814    let grandchild_signal = child_signal.child_signal();
815    let grandchild2_signal = child_signal.child_signal();
816
817    child_signal.send(SignalKind::SIGABRT);
818
819    assert!(parent_signal.aborted_code().is_none());
820    assert!(sibling_signal.aborted_code().is_none());
821    assert!(child_signal.aborted_code().is_some());
822    assert!(grandchild_signal.aborted_code().is_some());
823    assert!(grandchild2_signal.aborted_code().is_some());
824  }
825
826  #[tokio::test]
827  async fn test_wait_aborted() {
828    let kill_signal = KillSignal::default();
829
830    // Spawn a task to send an aborting signal
831    let signal_sender = kill_signal.clone();
832    deno_unsync::spawn(async move {
833      signal_sender.send(SignalKind::SIGABRT);
834    });
835
836    // Wait for the aborting signal in the main task
837    let signal = kill_signal.wait_aborted().await;
838    assert_eq!(signal, SignalKind::SIGABRT);
839    assert!(kill_signal.aborted_code().is_some());
840  }
841
842  #[tokio::test]
843  async fn test_propagation_and_is_aborted_flag() {
844    let parent_signal = KillSignal::default();
845    let child_signal = parent_signal.child_signal();
846
847    assert!(parent_signal.aborted_code().is_none());
848    assert!(child_signal.aborted_code().is_none());
849
850    // Send an aborting signal from the parent
851    deno_unsync::spawn({
852      let parent_signal = parent_signal.clone();
853      async move {
854        parent_signal.send(SignalKind::SIGQUIT);
855      }
856    });
857
858    // Wait for the signal in the child
859    let signal = child_signal.wait_aborted().await;
860    assert_eq!(signal, SignalKind::SIGQUIT);
861    assert_eq!(parent_signal.aborted_code(), Some(128 + 3));
862    assert_eq!(child_signal.aborted_code(), Some(128 + 3));
863  }
864
865  #[tokio::test]
866  async fn test_dropped_child_signal_cleanup() {
867    let parent_signal = KillSignal::default();
868
869    // Create a child signal and immediately drop it
870    {
871      let child_signal = parent_signal.child_signal();
872      assert!(child_signal.aborted_code().is_none());
873    }
874
875    // Send a signal from the parent
876    deno_unsync::spawn({
877      let parent_signal = parent_signal.clone();
878      async move {
879        parent_signal.send(SignalKind::SIGTERM);
880      }
881    });
882
883    // Verify no panic occurred and the parent still functions
884    let signal = parent_signal.wait_any().await;
885    assert_eq!(signal, SignalKind::SIGTERM);
886  }
887
888  #[tokio::test]
889  async fn test_drop_guard() {
890    let parent_signal = KillSignal::default();
891
892    // Disarmed drop guard
893    {
894      let drop_guard = parent_signal.clone().drop_guard();
895      drop_guard.disarm();
896    }
897    assert_eq!(parent_signal.aborted_code(), None);
898
899    // Actually drop
900    {
901      let drop_guard = parent_signal.clone().drop_guard();
902      drop(drop_guard);
903    }
904    assert_eq!(
905      parent_signal.aborted_code(),
906      Some(SignalKind::SIGTERM.aborted_code())
907    );
908  }
909}