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}
290
291impl Command {
292  pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
293    Self {
294      program: program.as_ref().to_os_string(),
295      args: vec![program.as_ref().to_os_string()],
296      envs: CommandEnv::default(),
297      detached: false,
298      cwd: None,
299      stdin: Stdio::Inherit,
300      stdout: Stdio::Inherit,
301      stderr: Stdio::Inherit,
302      extra_handles: vec![],
303      kill_on_drop: false,
304      verbatim_arguments: false,
305    }
306  }
307
308  pub fn verbatim_arguments(&mut self, verbatim: bool) -> &mut Self {
309    self.verbatim_arguments = verbatim;
310    self
311  }
312
313  pub fn get_current_dir(&self) -> Option<&Path> {
314    self.cwd.as_deref().map(Path::new)
315  }
316
317  pub fn current_dir<S: AsRef<Path>>(&mut self, cwd: S) -> &mut Self {
318    self.cwd = Some(cwd.as_ref().to_path_buf().into_os_string());
319    self
320  }
321
322  pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
323    self.args.push(arg.as_ref().to_os_string());
324    self
325  }
326
327  pub fn args<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
328    &mut self,
329    args: I,
330  ) -> &mut Self {
331    self
332      .args
333      .extend(args.into_iter().map(|a| a.as_ref().to_os_string()));
334    self
335  }
336
337  pub fn env<S: AsRef<OsStr>, T: AsRef<OsStr>>(
338    &mut self,
339    key: S,
340    value: T,
341  ) -> &mut Self {
342    self.envs.set(key.as_ref(), value.as_ref());
343    self
344  }
345
346  pub fn get_program(&self) -> &OsStr {
347    self.program.as_os_str()
348  }
349
350  pub fn get_args(&self) -> impl Iterator<Item = &OsStr> {
351    self.args.iter().skip(1).map(|a| a.as_os_str())
352  }
353
354  pub fn envs<
355    I: IntoIterator<Item = (S, T)>,
356    S: AsRef<OsStr>,
357    T: AsRef<OsStr>,
358  >(
359    &mut self,
360    envs: I,
361  ) -> &mut Self {
362    for (k, v) in envs {
363      self.envs.set(k.as_ref(), v.as_ref());
364    }
365    self
366  }
367
368  pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Self {
369    self.kill_on_drop = kill_on_drop;
370    self
371  }
372
373  pub fn detached(&mut self) -> &mut Self {
374    self.detached = true;
375    self.kill_on_drop = false;
376    self
377  }
378
379  pub fn env_clear(&mut self) -> &mut Self {
380    self.envs.clear();
381    self
382  }
383
384  pub fn stdin(&mut self, stdin: Stdio) -> &mut Self {
385    self.stdin = stdin;
386    self
387  }
388
389  pub fn stdout(&mut self, stdout: Stdio) -> &mut Self {
390    self.stdout = stdout;
391    self
392  }
393
394  pub fn stderr(&mut self, stderr: Stdio) -> &mut Self {
395    self.stderr = stderr;
396    self
397  }
398
399  pub fn extra_handle(&mut self, handle: Option<HANDLE>) -> &mut Self {
400    self.extra_handles.push(handle);
401    self
402  }
403
404  pub fn spawn(&mut self) -> Result<Child, std::io::Error> {
405    let mut flags = 0;
406    if self.detached {
407      flags |= uv_process_flags::Detached;
408    }
409    if self.verbatim_arguments {
410      flags |= uv_process_flags::WindowsVerbatimArguments;
411    }
412
413    let (stdin, child_stdin) = match self.stdin {
414      Stdio::Pipe => {
415        let pipes = crate::anon_pipe::anon_pipe(false, true)?;
416        let child_stdin_handle = pipes.ours.into_handle();
417        let stdin_handle = pipes.theirs.into_handle().into_raw_handle();
418
419        (
420          StdioContainer::RawHandle(stdin_handle),
421          Some(ChildStdin::from(child_stdin_handle)),
422        )
423      }
424      Stdio::Null => (StdioContainer::Ignore, None),
425      Stdio::Inherit => (StdioContainer::InheritFd(0), None),
426      Stdio::RawHandle(handle) => (StdioContainer::RawHandle(handle), None),
427    };
428    let (stdout, child_stdout) = match self.stdout {
429      Stdio::Pipe => {
430        let pipes = crate::anon_pipe::anon_pipe(true, true)?;
431        let child_stdout_handle = pipes.ours.into_handle();
432        let stdout_handle = pipes.theirs.into_handle().into_raw_handle();
433
434        (
435          StdioContainer::RawHandle(stdout_handle),
436          Some(ChildStdout::from(child_stdout_handle)),
437        )
438      }
439      Stdio::Null => (StdioContainer::Ignore, None),
440      Stdio::Inherit => (StdioContainer::InheritFd(1), None),
441      Stdio::RawHandle(handle) => (StdioContainer::RawHandle(handle), None),
442    };
443    let (stderr, child_stderr) = match self.stderr {
444      Stdio::Pipe => {
445        let pipes = crate::anon_pipe::anon_pipe(true, true)?;
446        let child_stderr_handle = pipes.ours.into_handle();
447        let stderr_handle = pipes.theirs.into_handle().into_raw_handle();
448
449        (
450          StdioContainer::RawHandle(stderr_handle),
451          Some(ChildStderr::from(child_stderr_handle)),
452        )
453      }
454      Stdio::Null => (StdioContainer::Ignore, None),
455      Stdio::Inherit => (StdioContainer::InheritFd(2), None),
456      Stdio::RawHandle(handle) => (StdioContainer::RawHandle(handle), None),
457    };
458
459    let mut stdio = Vec::with_capacity(3 + self.extra_handles.len());
460    stdio.extend([stdin, stdout, stderr]);
461    stdio.extend(self.extra_handles.iter().map(|h| {
462      h.map(StdioContainer::RawHandle)
463        .unwrap_or(StdioContainer::Ignore)
464    }));
465
466    crate::process::spawn(&SpawnOptions {
467      flags,
468      file: Cow::Borrowed(&self.program),
469      args: self
470        .args
471        .iter()
472        .map(|a| Cow::Borrowed(a.as_os_str()))
473        .collect(),
474      env: &self.envs,
475      cwd: self.cwd.as_deref().map(Cow::Borrowed),
476      stdio,
477    })
478    .map_err(|err| std::io::Error::other(err.to_string()))
479    .map(|process| Child {
480      inner: FusedChild::Child(ChildDropGuard {
481        inner: process,
482        kill_on_drop: self.kill_on_drop,
483      }),
484      stdin: child_stdin,
485      stdout: child_stdout,
486      stderr: child_stderr,
487    })
488  }
489}