Skip to main content

deno_subprocess_windows/
lib.rs

1// Copyright 2018-2026 the Deno authors. MIT license.
2
3// Parts adapted from tokio, license below
4// MIT License
5//
6// Copyright (c) Tokio Contributors
7//
8// Permission is hereby granted, free of charge, to any person obtaining a copy
9// of this software and associated documentation files (the "Software"), to deal
10// in the Software without restriction, including without limitation the rights
11// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12// copies of the Software, and to permit persons to whom the Software is
13// furnished to do so, subject to the following conditions:
14//
15// The above copyright notice and this permission notice shall be included in all
16// copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24// SOFTWARE.
25
26#![allow(
27  clippy::undocumented_unsafe_blocks,
28  reason = "ported code from std/tokio with existing safety context"
29)]
30#![cfg(windows)]
31
32#[cfg(test)]
33mod tests;
34
35mod process;
36mod process_stdio;
37
38mod anon_pipe;
39mod env;
40mod uv_error;
41mod widestr;
42
43use std::borrow::Cow;
44use std::ffi::OsStr;
45use std::ffi::OsString;
46use std::future::Future;
47use std::io;
48use std::io::Read;
49use std::os::windows::io::FromRawHandle;
50use std::os::windows::io::IntoRawHandle;
51use std::os::windows::process::ExitStatusExt;
52use std::os::windows::raw::HANDLE;
53use std::path::Path;
54use std::pin::Pin;
55use std::process::ChildStderr;
56use std::process::ChildStdin;
57use std::process::ChildStdout;
58use std::process::ExitStatus;
59use std::process::Output;
60use std::task::Context;
61use std::task::Poll;
62
63use anon_pipe::read2;
64use env::CommandEnv;
65pub use process::process_kill;
66pub use process_stdio::disable_stdio_inheritance;
67
68use crate::process::*;
69use crate::process_stdio::*;
70
71#[derive(Debug)]
72pub enum Stdio {
73  Inherit,
74  Pipe,
75  Null,
76  RawHandle(HANDLE),
77}
78
79impl From<Stdio> for std::process::Stdio {
80  fn from(stdio: Stdio) -> Self {
81    match stdio {
82      Stdio::Inherit => std::process::Stdio::inherit(),
83      Stdio::Pipe => std::process::Stdio::piped(),
84      Stdio::Null => std::process::Stdio::null(),
85      Stdio::RawHandle(handle) => unsafe {
86        std::process::Stdio::from_raw_handle(handle)
87      },
88    }
89  }
90}
91
92impl Stdio {
93  pub fn inherit() -> Self {
94    Stdio::Inherit
95  }
96
97  pub fn piped() -> Self {
98    Stdio::Pipe
99  }
100
101  pub fn null() -> Self {
102    Stdio::Null
103  }
104}
105
106impl<T: IntoRawHandle> From<T> for Stdio {
107  fn from(value: T) -> Self {
108    Stdio::RawHandle(value.into_raw_handle())
109  }
110}
111
112pub struct Child {
113  inner: FusedChild,
114  pub stdin: Option<ChildStdin>,
115  pub stdout: Option<ChildStdout>,
116  pub stderr: Option<ChildStderr>,
117}
118
119/// An interface for killing a running process.
120/// Copied from https://github.com/tokio-rs/tokio/blob/ab8d7b82a1252b41dc072f641befb6d2afcb3373/tokio/src/process/kill.rs
121pub(crate) trait Kill {
122  /// Forcefully kills the process.
123  fn kill(&mut self) -> io::Result<()>;
124}
125
126impl<T: Kill> Kill for &mut T {
127  fn kill(&mut self) -> io::Result<()> {
128    (**self).kill()
129  }
130}
131
132/// A drop guard which can ensure the child process is killed on drop if specified.
133///
134/// From https://github.com/tokio-rs/tokio/blob/ab8d7b82a1252b41dc072f641befb6d2afcb3373/tokio/src/process/mod.rs
135#[derive(Debug)]
136struct ChildDropGuard<T: Kill> {
137  inner: T,
138  kill_on_drop: bool,
139}
140
141impl<T: Kill> Kill for ChildDropGuard<T> {
142  fn kill(&mut self) -> io::Result<()> {
143    let ret = self.inner.kill();
144
145    if ret.is_ok() {
146      self.kill_on_drop = false;
147    }
148
149    ret
150  }
151}
152
153impl<T: Kill> Drop for ChildDropGuard<T> {
154  fn drop(&mut self) {
155    if self.kill_on_drop {
156      drop(self.kill());
157    }
158  }
159}
160
161// copied from https://github.com/tokio-rs/tokio/blob/ab8d7b82a1252b41dc072f641befb6d2afcb3373/tokio/src/process/mod.rs
162impl<T, E, F> Future for ChildDropGuard<F>
163where
164  F: Future<Output = Result<T, E>> + Kill + Unpin,
165{
166  type Output = Result<T, E>;
167
168  fn poll(
169    mut self: Pin<&mut Self>,
170    cx: &mut Context<'_>,
171  ) -> Poll<Self::Output> {
172    let ret = Pin::new(&mut self.inner).poll(cx);
173
174    if let Poll::Ready(Ok(_)) = ret {
175      // Avoid the overhead of trying to kill a reaped process
176      self.kill_on_drop = false;
177    }
178
179    ret
180  }
181}
182
183/// Keeps track of the exit status of a child process without worrying about
184/// polling the underlying futures even after they have completed.
185///
186/// From https://github.com/tokio-rs/tokio/blob/ab8d7b82a1252b41dc072f641befb6d2afcb3373/tokio/src/process/mod.rs
187#[derive(Debug)]
188enum FusedChild {
189  Child(ChildDropGuard<ChildProcess>),
190  Done(i32),
191}
192
193impl Child {
194  pub fn id(&self) -> Option<u32> {
195    match &self.inner {
196      FusedChild::Child(child) => Some(child.inner.pid() as u32),
197      FusedChild::Done(_) => None,
198    }
199  }
200
201  pub fn wait_blocking(&mut self) -> Result<ExitStatus, std::io::Error> {
202    drop(self.stdin.take());
203    match &mut self.inner {
204      FusedChild::Child(child) => child
205        .inner
206        .wait()
207        .map(|code| ExitStatus::from_raw(code as u32)),
208      FusedChild::Done(code) => Ok(ExitStatus::from_raw(*code as u32)),
209    }
210  }
211
212  pub async fn wait(&mut self) -> io::Result<ExitStatus> {
213    // Ensure stdin is closed so the child isn't stuck waiting on
214    // input while the parent is waiting for it to exit.
215    drop(self.stdin.take());
216
217    match &mut self.inner {
218      FusedChild::Done(exit) => Ok(ExitStatus::from_raw(*exit as u32)),
219      FusedChild::Child(child) => {
220        let ret = child.await;
221
222        if let Ok(exit) = ret {
223          self.inner = FusedChild::Done(exit);
224        }
225
226        ret.map(|code| ExitStatus::from_raw(code as u32))
227      }
228    }
229  }
230
231  pub fn try_wait(&mut self) -> Result<Option<i32>, std::io::Error> {
232    match &mut self.inner {
233      FusedChild::Done(exit) => Ok(Some(*exit)),
234      FusedChild::Child(child) => child.inner.try_wait(),
235    }
236  }
237
238  // from std
239  pub fn wait_with_output(&mut self) -> io::Result<Output> {
240    drop(self.stdin.take());
241
242    let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
243    match (self.stdout.take(), self.stderr.take()) {
244      (None, None) => {}
245      (Some(mut out), None) => {
246        let res = out.read_to_end(&mut stdout);
247        res.unwrap();
248      }
249      (None, Some(mut err)) => {
250        let res = err.read_to_end(&mut stderr);
251        res.unwrap();
252      }
253      (Some(out), Some(err)) => {
254        let res = read2(
255          unsafe {
256            crate::anon_pipe::AnonPipe::from_raw_handle(out.into_raw_handle())
257          },
258          &mut stdout,
259          unsafe {
260            crate::anon_pipe::AnonPipe::from_raw_handle(err.into_raw_handle())
261          },
262          &mut stderr,
263        );
264        res.unwrap();
265      }
266    }
267
268    let status = self.wait_blocking()?;
269    Ok(Output {
270      status,
271      stdout,
272      stderr,
273    })
274  }
275}
276
277pub struct Command {
278  program: OsString,
279  args: Vec<OsString>,
280  envs: CommandEnv,
281  detached: bool,
282  cwd: Option<OsString>,
283  stdin: Stdio,
284  stdout: Stdio,
285  stderr: Stdio,
286  extra_handles: Vec<Option<HANDLE>>,
287  kill_on_drop: bool,
288  verbatim_arguments: bool,
289  windows_hide: bool,
290}
291
292impl Command {
293  pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
294    Self {
295      program: program.as_ref().to_os_string(),
296      args: vec![program.as_ref().to_os_string()],
297      envs: CommandEnv::default(),
298      detached: false,
299      cwd: None,
300      stdin: Stdio::Inherit,
301      stdout: Stdio::Inherit,
302      stderr: Stdio::Inherit,
303      extra_handles: vec![],
304      kill_on_drop: false,
305      verbatim_arguments: false,
306      windows_hide: false,
307    }
308  }
309
310  pub fn verbatim_arguments(&mut self, verbatim: bool) -> &mut Self {
311    self.verbatim_arguments = verbatim;
312    self
313  }
314
315  pub fn windows_hide(&mut self, hide: bool) -> &mut Self {
316    self.windows_hide = hide;
317    self
318  }
319
320  pub fn get_current_dir(&self) -> Option<&Path> {
321    self.cwd.as_deref().map(Path::new)
322  }
323
324  pub fn current_dir<S: AsRef<Path>>(&mut self, cwd: S) -> &mut Self {
325    self.cwd = Some(cwd.as_ref().to_path_buf().into_os_string());
326    self
327  }
328
329  pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
330    self.args.push(arg.as_ref().to_os_string());
331    self
332  }
333
334  pub fn args<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
335    &mut self,
336    args: I,
337  ) -> &mut Self {
338    self
339      .args
340      .extend(args.into_iter().map(|a| a.as_ref().to_os_string()));
341    self
342  }
343
344  pub fn env<S: AsRef<OsStr>, T: AsRef<OsStr>>(
345    &mut self,
346    key: S,
347    value: T,
348  ) -> &mut Self {
349    self.envs.set(key.as_ref(), value.as_ref());
350    self
351  }
352
353  pub fn get_program(&self) -> &OsStr {
354    self.program.as_os_str()
355  }
356
357  pub fn get_args(&self) -> impl Iterator<Item = &OsStr> {
358    self.args.iter().skip(1).map(|a| a.as_os_str())
359  }
360
361  pub fn envs<
362    I: IntoIterator<Item = (S, T)>,
363    S: AsRef<OsStr>,
364    T: AsRef<OsStr>,
365  >(
366    &mut self,
367    envs: I,
368  ) -> &mut Self {
369    for (k, v) in envs {
370      self.envs.set(k.as_ref(), v.as_ref());
371    }
372    self
373  }
374
375  pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Self {
376    self.kill_on_drop = kill_on_drop;
377    self
378  }
379
380  pub fn detached(&mut self) -> &mut Self {
381    self.detached = true;
382    self.kill_on_drop = false;
383    self
384  }
385
386  pub fn env_clear(&mut self) -> &mut Self {
387    self.envs.clear();
388    self
389  }
390
391  pub fn stdin(&mut self, stdin: Stdio) -> &mut Self {
392    self.stdin = stdin;
393    self
394  }
395
396  pub fn stdout(&mut self, stdout: Stdio) -> &mut Self {
397    self.stdout = stdout;
398    self
399  }
400
401  pub fn stderr(&mut self, stderr: Stdio) -> &mut Self {
402    self.stderr = stderr;
403    self
404  }
405
406  pub fn extra_handle(&mut self, handle: Option<HANDLE>) -> &mut Self {
407    self.extra_handles.push(handle);
408    self
409  }
410
411  pub fn spawn(&mut self) -> Result<Child, std::io::Error> {
412    let mut flags = 0;
413    if self.detached {
414      flags |= uv_process_flags::Detached;
415    }
416    if self.verbatim_arguments {
417      flags |= uv_process_flags::WindowsVerbatimArguments;
418    }
419    if self.windows_hide {
420      flags |= uv_process_flags::WindowsHide;
421    }
422
423    let (stdin, child_stdin) = match self.stdin {
424      Stdio::Pipe => {
425        let pipes = crate::anon_pipe::anon_pipe(false, true)?;
426        let child_stdin_handle = pipes.ours.into_handle();
427        let stdin_handle = pipes.theirs.into_handle().into_raw_handle();
428
429        (
430          StdioContainer::RawHandle(stdin_handle),
431          Some(ChildStdin::from(child_stdin_handle)),
432        )
433      }
434      Stdio::Null => (StdioContainer::Ignore, None),
435      Stdio::Inherit => (StdioContainer::InheritFd(0), None),
436      Stdio::RawHandle(handle) => (StdioContainer::RawHandle(handle), None),
437    };
438    let (stdout, child_stdout) = match self.stdout {
439      Stdio::Pipe => {
440        let pipes = crate::anon_pipe::anon_pipe(true, true)?;
441        let child_stdout_handle = pipes.ours.into_handle();
442        let stdout_handle = pipes.theirs.into_handle().into_raw_handle();
443
444        (
445          StdioContainer::RawHandle(stdout_handle),
446          Some(ChildStdout::from(child_stdout_handle)),
447        )
448      }
449      Stdio::Null => (StdioContainer::Ignore, None),
450      Stdio::Inherit => (StdioContainer::InheritFd(1), None),
451      Stdio::RawHandle(handle) => (StdioContainer::RawHandle(handle), None),
452    };
453    let (stderr, child_stderr) = match self.stderr {
454      Stdio::Pipe => {
455        let pipes = crate::anon_pipe::anon_pipe(true, true)?;
456        let child_stderr_handle = pipes.ours.into_handle();
457        let stderr_handle = pipes.theirs.into_handle().into_raw_handle();
458
459        (
460          StdioContainer::RawHandle(stderr_handle),
461          Some(ChildStderr::from(child_stderr_handle)),
462        )
463      }
464      Stdio::Null => (StdioContainer::Ignore, None),
465      Stdio::Inherit => (StdioContainer::InheritFd(2), None),
466      Stdio::RawHandle(handle) => (StdioContainer::RawHandle(handle), None),
467    };
468
469    let mut stdio = Vec::with_capacity(3 + self.extra_handles.len());
470    stdio.extend([stdin, stdout, stderr]);
471    stdio.extend(self.extra_handles.iter().map(|h| {
472      h.map(StdioContainer::RawHandle)
473        .unwrap_or(StdioContainer::Ignore)
474    }));
475
476    crate::process::spawn(&SpawnOptions {
477      flags,
478      file: Cow::Borrowed(&self.program),
479      args: self
480        .args
481        .iter()
482        .map(|a| Cow::Borrowed(a.as_os_str()))
483        .collect(),
484      env: &self.envs,
485      cwd: self.cwd.as_deref().map(Cow::Borrowed),
486      stdio,
487    })
488    .map(|process| Child {
489      inner: FusedChild::Child(ChildDropGuard {
490        inner: process,
491        kill_on_drop: self.kill_on_drop,
492      }),
493      stdin: child_stdin,
494      stdout: child_stdout,
495      stderr: child_stderr,
496    })
497  }
498}