Rollup merge of #115501 - michaelvanstraten:set_inherit_handles, r=ChrisDenton

Add new inherit_handles flag to CommandExt trait

This PR adds a new flag to the [`CommandExt`](https://doc.rust-lang.org/stable/std/os/windows/process/trait.CommandExt.html) trait to set whether to inherit the handles of the calling process ([ref][1]).

This is necessary when, for example, spawning a process with a `pseudoconsole` attached.

r? ``@ChrisDenton``

ACP: https://github.com/rust-lang/libs-team/issues/264
[1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw>
This commit is contained in:
Stuart Cook 2025-10-23 12:06:30 +11:00 committed by GitHub
commit e4bf8b3fc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 108 additions and 1 deletions

View file

@ -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<bool>) -> &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")]

View file

@ -159,6 +159,7 @@ pub struct Command {
startupinfo_fullscreen: bool,
startupinfo_untrusted_source: bool,
startupinfo_force_feedback: Option<bool>,
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,

View file

@ -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::*;