/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
runtime/subprocess_windows/src/lib.rs
499 строк
13 KB
em
fix(ext/node): honor windowsHide in child_process spawn (#34627)
31 май 2026, 19:05
Не верифицирован
31 май 2026, 19:05
d11c5d0
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. // Parts adapted from tokio, license below // MIT License // // Copyright (c) Tokio Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #![allow( clippy::undocumented_unsafe_blocks, reason = "ported code from std/tokio with existing safety context" )] #![cfg(windows)] #[cfg(test)] mod tests; mod process; mod process_stdio; mod anon_pipe; mod env; mod uv_error; mod widestr; use std::borrow::Cow; use std::ffi::OsStr; use std::ffi::OsString; use std::future::Future; use std::io; use std::io::Read; use std::os::windows::io::FromRawHandle; use std::os::windows::io::IntoRawHandle; use std::os::windows::process::ExitStatusExt; use std::os::windows::raw::HANDLE; use std::path::Path; use std::pin::Pin; use std::process::ChildStderr; use std::process::ChildStdin; use std::process::ChildStdout; use std::process::ExitStatus; use std::process::Output; use std::task::Context; use std::task::Poll; use anon_pipe::read2; use env::CommandEnv; pub use process::process_kill; pub use process_stdio::disable_stdio_inheritance; use crate::process::*; use crate::process_stdio::*; #[derive(Debug)] pub enum Stdio { Inherit, Pipe, Null, RawHandle(HANDLE), } impl From<Stdio> for std::process::Stdio { fn from(stdio: Stdio) -> Self { match stdio { Stdio::Inherit => std::process::Stdio::inherit(), Stdio::Pipe => std::process::Stdio::piped(), Stdio::Null => std::process::Stdio::null(), Stdio::RawHandle(handle) => unsafe { std::process::Stdio::from_raw_handle(handle) }, } } } impl Stdio { pub fn inherit() -> Self { Stdio::Inherit } pub fn piped() -> Self { Stdio::Pipe } pub fn null() -> Self { Stdio::Null } } impl<T: IntoRawHandle> From<T> for Stdio { fn from(value: T) -> Self { Stdio::RawHandle(value.into_raw_handle()) } } pub struct Child { inner: FusedChild, pub stdin: Option<ChildStdin>, pub stdout: Option<ChildStdout>, pub stderr: Option<ChildStderr>, } /// An interface for killing a running process. /// Copied from https://github.com/tokio-rs/tokio/blob/ab8d7b82a1252b41dc072f641befb6d2afcb3373/tokio/src/process/kill.rs pub(crate) trait Kill { /// Forcefully kills the process. fn kill(&mut self) -> io::Result<()>; } impl<T: Kill> Kill for &mut T { fn kill(&mut self) -> io::Result<()> { (**self).kill() } } /// A drop guard which can ensure the child process is killed on drop if specified. /// /// From https://github.com/tokio-rs/tokio/blob/ab8d7b82a1252b41dc072f641befb6d2afcb3373/tokio/src/process/mod.rs #[derive(Debug)] struct ChildDropGuard<T: Kill> { inner: T, kill_on_drop: bool, } impl<T: Kill> Kill for ChildDropGuard<T> { fn kill(&mut self) -> io::Result<()> { let ret = self.inner.kill(); if ret.is_ok() { self.kill_on_drop = false; } ret } } impl<T: Kill> Drop for ChildDropGuard<T> { fn drop(&mut self) { if self.kill_on_drop { drop(self.kill()); } } } // copied from https://github.com/tokio-rs/tokio/blob/ab8d7b82a1252b41dc072f641befb6d2afcb3373/tokio/src/process/mod.rs impl<T, E, F> Future for ChildDropGuard<F> where F: Future<Output = Result<T, E>> + Kill + Unpin, { type Output = Result<T, E>; fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll<Self::Output> { let ret = Pin::new(&mut self.inner).poll(cx); if let Poll::Ready(Ok(_)) = ret { // Avoid the overhead of trying to kill a reaped process self.kill_on_drop = false; } ret } } /// Keeps track of the exit status of a child process without worrying about /// polling the underlying futures even after they have completed. /// /// From https://github.com/tokio-rs/tokio/blob/ab8d7b82a1252b41dc072f641befb6d2afcb3373/tokio/src/process/mod.rs #[derive(Debug)] enum FusedChild { Child(ChildDropGuard<ChildProcess>), Done(i32), } impl Child { pub fn id(&self) -> Option<u32> { match &self.inner { FusedChild::Child(child) => Some(child.inner.pid() as u32), FusedChild::Done(_) => None, } } pub fn wait_blocking(&mut self) -> Result<ExitStatus, std::io::Error> { drop(self.stdin.take()); match &mut self.inner { FusedChild::Child(child) => child .inner .wait() .map(|code| ExitStatus::from_raw(code as u32)), FusedChild::Done(code) => Ok(ExitStatus::from_raw(*code as u32)), } } pub async fn wait(&mut self) -> io::Result<ExitStatus> { // Ensure stdin is closed so the child isn't stuck waiting on // input while the parent is waiting for it to exit. drop(self.stdin.take()); match &mut self.inner { FusedChild::Done(exit) => Ok(ExitStatus::from_raw(*exit as u32)), FusedChild::Child(child) => { let ret = child.await; if let Ok(exit) = ret { self.inner = FusedChild::Done(exit); } ret.map(|code| ExitStatus::from_raw(code as u32)) } } } pub fn try_wait(&mut self) -> Result<Option<i32>, std::io::Error> { match &mut self.inner { FusedChild::Done(exit) => Ok(Some(*exit)), FusedChild::Child(child) => child.inner.try_wait(), } } // from std pub fn wait_with_output(&mut self) -> io::Result<Output> { drop(self.stdin.take()); let (mut stdout, mut stderr) = (Vec::new(), Vec::new()); match (self.stdout.take(), self.stderr.take()) { (None, None) => {} (Some(mut out), None) => { let res = out.read_to_end(&mut stdout); res.unwrap(); } (None, Some(mut err)) => { let res = err.read_to_end(&mut stderr); res.unwrap(); } (Some(out), Some(err)) => { let res = read2( unsafe { crate::anon_pipe::AnonPipe::from_raw_handle(out.into_raw_handle()) }, &mut stdout, unsafe { crate::anon_pipe::AnonPipe::from_raw_handle(err.into_raw_handle()) }, &mut stderr, ); res.unwrap(); } } let status = self.wait_blocking()?; Ok(Output { status, stdout, stderr, }) } } pub struct Command { program: OsString, args: Vec<OsString>, envs: CommandEnv, detached: bool, cwd: Option<OsString>, stdin: Stdio, stdout: Stdio, stderr: Stdio, extra_handles: Vec<Option<HANDLE>>, kill_on_drop: bool, verbatim_arguments: bool, windows_hide: bool, } impl Command { pub fn new<S: AsRef<OsStr>>(program: S) -> Self { Self { program: program.as_ref().to_os_string(), args: vec![program.as_ref().to_os_string()], envs: CommandEnv::default(), detached: false, cwd: None, stdin: Stdio::Inherit, stdout: Stdio::Inherit, stderr: Stdio::Inherit, extra_handles: vec![], kill_on_drop: false, verbatim_arguments: false, windows_hide: false, } } pub fn verbatim_arguments(&mut self, verbatim: bool) -> &mut Self { self.verbatim_arguments = verbatim; self } pub fn windows_hide(&mut self, hide: bool) -> &mut Self { self.windows_hide = hide; self } pub fn get_current_dir(&self) -> Option<&Path> { self.cwd.as_deref().map(Path::new) } pub fn current_dir<S: AsRef<Path>>(&mut self, cwd: S) -> &mut Self { self.cwd = Some(cwd.as_ref().to_path_buf().into_os_string()); self } pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self { self.args.push(arg.as_ref().to_os_string()); self } pub fn args<I: IntoIterator<Item = S>, S: AsRef<OsStr>>( &mut self, args: I, ) -> &mut Self { self .args .extend(args.into_iter().map(|a| a.as_ref().to_os_string())); self } pub fn env<S: AsRef<OsStr>, T: AsRef<OsStr>>( &mut self, key: S, value: T, ) -> &mut Self { self.envs.set(key.as_ref(), value.as_ref()); self } pub fn get_program(&self) -> &OsStr { self.program.as_os_str() } pub fn get_args(&self) -> impl Iterator<Item = &OsStr> { self.args.iter().skip(1).map(|a| a.as_os_str()) } pub fn envs< I: IntoIterator<Item = (S, T)>, S: AsRef<OsStr>, T: AsRef<OsStr>, >( &mut self, envs: I, ) -> &mut Self { for (k, v) in envs { self.envs.set(k.as_ref(), v.as_ref()); } self } pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Self { self.kill_on_drop = kill_on_drop; self } pub fn detached(&mut self) -> &mut Self { self.detached = true; self.kill_on_drop = false; self } pub fn env_clear(&mut self) -> &mut Self { self.envs.clear(); self } pub fn stdin(&mut self, stdin: Stdio) -> &mut Self { self.stdin = stdin; self } pub fn stdout(&mut self, stdout: Stdio) -> &mut Self { self.stdout = stdout; self } pub fn stderr(&mut self, stderr: Stdio) -> &mut Self { self.stderr = stderr; self } pub fn extra_handle(&mut self, handle: Option<HANDLE>) -> &mut Self { self.extra_handles.push(handle); self } pub fn spawn(&mut self) -> Result<Child, std::io::Error> { let mut flags = 0; if self.detached { flags |= uv_process_flags::Detached; } if self.verbatim_arguments { flags |= uv_process_flags::WindowsVerbatimArguments; } if self.windows_hide { flags |= uv_process_flags::WindowsHide; } let (stdin, child_stdin) = match self.stdin { Stdio::Pipe => { let pipes = crate::anon_pipe::anon_pipe(false, true)?; let child_stdin_handle = pipes.ours.into_handle(); let stdin_handle = pipes.theirs.into_handle().into_raw_handle(); ( StdioContainer::RawHandle(stdin_handle), Some(ChildStdin::from(child_stdin_handle)), ) } Stdio::Null => (StdioContainer::Ignore, None), Stdio::Inherit => (StdioContainer::InheritFd(0), None), Stdio::RawHandle(handle) => (StdioContainer::RawHandle(handle), None), }; let (stdout, child_stdout) = match self.stdout { Stdio::Pipe => { let pipes = crate::anon_pipe::anon_pipe(true, true)?; let child_stdout_handle = pipes.ours.into_handle(); let stdout_handle = pipes.theirs.into_handle().into_raw_handle(); ( StdioContainer::RawHandle(stdout_handle), Some(ChildStdout::from(child_stdout_handle)), ) } Stdio::Null => (StdioContainer::Ignore, None), Stdio::Inherit => (StdioContainer::InheritFd(1), None), Stdio::RawHandle(handle) => (StdioContainer::RawHandle(handle), None), }; let (stderr, child_stderr) = match self.stderr { Stdio::Pipe => { let pipes = crate::anon_pipe::anon_pipe(true, true)?; let child_stderr_handle = pipes.ours.into_handle(); let stderr_handle = pipes.theirs.into_handle().into_raw_handle(); ( StdioContainer::RawHandle(stderr_handle), Some(ChildStderr::from(child_stderr_handle)), ) } Stdio::Null => (StdioContainer::Ignore, None), Stdio::Inherit => (StdioContainer::InheritFd(2), None), Stdio::RawHandle(handle) => (StdioContainer::RawHandle(handle), None), }; let mut stdio = Vec::with_capacity(3 + self.extra_handles.len()); stdio.extend([stdin, stdout, stderr]); stdio.extend(self.extra_handles.iter().map(|h| { h.map(StdioContainer::RawHandle) .unwrap_or(StdioContainer::Ignore) })); crate::process::spawn(&SpawnOptions { flags, file: Cow::Borrowed(&self.program), args: self .args .iter() .map(|a| Cow::Borrowed(a.as_os_str())) .collect(), env: &self.envs, cwd: self.cwd.as_deref().map(Cow::Borrowed), stdio, }) .map_err(|err| std::io::Error::other(err.to_string())) .map(|process| Child { inner: FusedChild::Child(ChildDropGuard { inner: process, kill_on_drop: self.kill_on_drop, }), stdin: child_stdin, stdout: child_stdout, stderr: child_stderr, }) } }