diff --git a/library/std/src/os/windows/process.rs b/library/std/src/os/windows/process.rs index c223eee95b5f5..f21ed51606f6d 100644 --- a/library/std/src/os/windows/process.rs +++ b/library/std/src/os/windows/process.rs @@ -365,6 +365,20 @@ pub trait CommandExt: Sealed { /// [1]: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa #[unstable(feature = "windows_process_extensions_startupinfo", issue = "141010")] fn startupinfo_force_feedback(&mut self, enabled: Option) -> &mut process::Command; + + /// If this flag is set to `true`, each inheritable handle in the calling + /// process is inherited by the new process. If the flag is `false`, the + /// handles are not inherited. + /// + /// The default value for this flag is `true`. + /// + /// **Note** that inherited handles have the same value and access rights + /// as the original handles. For additional discussion of inheritable handles, + /// see the [Remarks][1] section of the `CreateProcessW` documentation. + /// + /// [1]: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw#remarks + #[unstable(feature = "windows_process_extensions_inherit_handles", issue = "146407")] + fn inherit_handles(&mut self, inherit_handles: bool) -> &mut process::Command; } #[stable(feature = "windows_process_extensions", since = "1.16.0")] @@ -421,6 +435,11 @@ impl CommandExt for process::Command { self.as_inner_mut().startupinfo_force_feedback(enabled); self } + + fn inherit_handles(&mut self, inherit_handles: bool) -> &mut process::Command { + self.as_inner_mut().inherit_handles(inherit_handles); + self + } } #[unstable(feature = "windows_process_extensions_main_thread_handle", issue = "96723")] diff --git a/library/std/src/sys/process/windows.rs b/library/std/src/sys/process/windows.rs index 1f2001bdc2040..7d58093c54bbf 100644 --- a/library/std/src/sys/process/windows.rs +++ b/library/std/src/sys/process/windows.rs @@ -159,6 +159,7 @@ pub struct Command { startupinfo_fullscreen: bool, startupinfo_untrusted_source: bool, startupinfo_force_feedback: Option, + inherit_handles: bool, } pub enum Stdio { @@ -187,6 +188,7 @@ impl Command { startupinfo_fullscreen: false, startupinfo_untrusted_source: false, startupinfo_force_feedback: None, + inherit_handles: true, } } @@ -252,6 +254,10 @@ impl Command { self.cwd.as_ref().map(Path::new) } + pub fn inherit_handles(&mut self, inherit_handles: bool) { + self.inherit_handles = inherit_handles; + } + pub fn spawn( &mut self, default: Stdio, @@ -310,6 +316,7 @@ impl Command { flags |= c::DETACHED_PROCESS | c::CREATE_NEW_PROCESS_GROUP; } + let inherit_handles = self.inherit_handles as c::BOOL; let (envp, _data) = make_envp(maybe_env)?; let (dirp, _data) = make_dirp(self.cwd.as_ref())?; let mut pi = zeroed_process_information(); @@ -401,7 +408,7 @@ impl Command { cmd_str.as_mut_ptr(), ptr::null_mut(), ptr::null_mut(), - c::TRUE, + inherit_handles, flags, envp, dirp, diff --git a/tests/ui/process/win-inherit-handles.rs b/tests/ui/process/win-inherit-handles.rs new file mode 100644 index 0000000000000..5750a4b40f055 --- /dev/null +++ b/tests/ui/process/win-inherit-handles.rs @@ -0,0 +1,81 @@ +// Tests `inherit_handles` by spawning a child process and checking its handle +// count to be greater than when not setting the option. + +//@ run-pass +//@ only-windows +//@ needs-subprocess +//@ edition: 2024 + +#![feature(windows_process_extensions_inherit_handles)] + +use std::os::windows::io::AsRawHandle; +use std::os::windows::process::CommandExt; +use std::process::{Command, Stdio}; +use std::time::Duration; +use std::{env, io, thread}; + +fn main() { + if std::env::args().skip(1).any(|s| s == "--child") { + child(); + } else { + parent(); + } +} + +fn parent() { + let with_inherit_count = child_handle_count(true); + let without_inherit_count = child_handle_count(false); + // Only compare the two values instead of only expecting a hard 1 for + // robustness, although only 1 has ever been observed here. + assert!( + with_inherit_count > without_inherit_count, + "Child process handle count unexpectedly smaller when inheriting handles compared to when \ + not: {} <= {}", + with_inherit_count, + without_inherit_count, + ); +} + +/// Spawns the current program as a child process and returns its handle count. +fn child_handle_count(inherit_handles: bool) -> u32 { + let mut child_proc = Command::new(&env::current_exe().unwrap()) + .arg("--child") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .inherit_handles(inherit_handles) + .spawn() + .unwrap(); + + let mut handle_count = 0; + let ret = unsafe { GetProcessHandleCount(child_proc.as_raw_handle(), &raw mut handle_count) }; + assert_ne!( + ret, + 0, + "GetProcessHandleCount failed: {:?}", + io::Error::last_os_error(), + ); + + // Cleanup. + child_proc.kill().unwrap(); + child_proc.wait().unwrap(); + + handle_count +} + +/// A process that stays running until killed. +fn child() { + // Don't wait forever if something goes wrong. + thread::sleep(Duration::from_secs(10)); +} + +// Windows API +mod winapi { + use std::os::windows::raw::HANDLE; + + #[link(name = "kernel32")] + unsafe extern "system" { + pub fn GetProcessHandleCount(hprocess: HANDLE, pdwhandlecount: *mut u32) -> i32; + } +} +use winapi::*;