Skip to content

Commit 9847c64

Browse files
committed
Auto merge of rust-lang#114848 - michaelvanstraten:spawn_with_attributes, r=ChrisDenton
Add ability to spawn Windows process with Proc Thread Attributes | Take 2 This is the second attempt to merge pull request rust-lang#88193 into the standard library. This PR implements the ability to add arbitrary attributes to a command on Windows targets using a new `raw_attribute` method on the [`CommandExt`](https://doc.rust-lang.org/stable/std/os/windows/process/trait.CommandExt.html) trait. `@TyPR124` and my main motivation behind adding this feature is to enable the support of pseudo terminals in the std library, but there are many more applications. A good starting point to get into this topic is to head over to the [`Win32 API documentation`](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute).
2 parents 1bd0430 + edefa8b commit 9847c64

File tree

5 files changed

+306
-2
lines changed

5 files changed

+306
-2
lines changed

library/std/src/os/windows/process.rs

+69
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,66 @@ pub trait CommandExt: Sealed {
192192
/// ```
193193
#[unstable(feature = "windows_process_extensions_async_pipes", issue = "98289")]
194194
fn async_pipes(&mut self, always_async: bool) -> &mut process::Command;
195+
196+
/// Sets a raw attribute on the command, providing extended configuration options for Windows processes.
197+
///
198+
/// This method allows you to specify custom attributes for a child process on Windows systems using raw attribute values.
199+
/// Raw attributes provide extended configurability for process creation, but their usage can be complex and potentially unsafe.
200+
///
201+
/// The `attribute` parameter specifies the raw attribute to be set, while the `value` parameter holds the value associated with that attribute.
202+
/// Please refer to the [`windows-rs`](https://microsoft.github.io/windows-docs-rs/doc/windows/) documentation or the [`Win32 API documentation`](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute) for detailed information about available attributes and their meanings.
203+
///
204+
/// # Note
205+
///
206+
/// The maximum number of raw attributes is the value of [`u32::MAX`].
207+
/// If this limit is exceeded, the call to [`process::Command::spawn`] will return an `Error` indicating that the maximum number of attributes has been exceeded.
208+
/// # Safety
209+
///
210+
/// The usage of raw attributes is potentially unsafe and should be done with caution. Incorrect attribute values or improper configuration can lead to unexpected behavior or errors.
211+
///
212+
/// # Example
213+
///
214+
/// The following example demonstrates how to create a child process with a specific parent process ID using a raw attribute.
215+
///
216+
/// ```rust
217+
/// #![feature(windows_process_extensions_raw_attribute)]
218+
/// use std::os::windows::{process::CommandExt, io::AsRawHandle};
219+
/// use std::process::Command;
220+
///
221+
/// # struct ProcessDropGuard(std::process::Child);
222+
/// # impl Drop for ProcessDropGuard {
223+
/// # fn drop(&mut self) {
224+
/// # let _ = self.0.kill();
225+
/// # }
226+
/// # }
227+
///
228+
/// let parent = Command::new("cmd").spawn()?;
229+
///
230+
/// let mut child_cmd = Command::new("cmd");
231+
///
232+
/// const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS: usize = 0x00020000;
233+
///
234+
/// unsafe {
235+
/// child_cmd.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.as_raw_handle() as isize);
236+
/// }
237+
/// #
238+
/// # let parent = ProcessDropGuard(parent);
239+
///
240+
/// let mut child = child_cmd.spawn()?;
241+
///
242+
/// # child.kill()?;
243+
/// # Ok::<(), std::io::Error>(())
244+
/// ```
245+
///
246+
/// # Safety Note
247+
///
248+
/// Remember that improper use of raw attributes can lead to undefined behavior or security vulnerabilities. Always consult the documentation and ensure proper attribute values are used.
249+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
250+
unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>(
251+
&mut self,
252+
attribute: usize,
253+
value: T,
254+
) -> &mut process::Command;
195255
}
196256

197257
#[stable(feature = "windows_process_extensions", since = "1.16.0")]
@@ -219,6 +279,15 @@ impl CommandExt for process::Command {
219279
let _ = always_async;
220280
self
221281
}
282+
283+
unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>(
284+
&mut self,
285+
attribute: usize,
286+
value: T,
287+
) -> &mut process::Command {
288+
self.as_inner_mut().raw_attribute(attribute, value);
289+
self
290+
}
222291
}
223292

224293
#[unstable(feature = "windows_process_extensions_main_thread_handle", issue = "96723")]

library/std/src/process/tests.rs

+85
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,91 @@ fn test_creation_flags() {
434434
assert!(events > 0);
435435
}
436436

437+
/// Tests proc thread attributes by spawning a process with a custom parent process,
438+
/// then comparing the parent process ID with the expected parent process ID.
439+
#[test]
440+
#[cfg(windows)]
441+
fn test_proc_thread_attributes() {
442+
use crate::mem;
443+
use crate::os::windows::io::AsRawHandle;
444+
use crate::os::windows::process::CommandExt;
445+
use crate::sys::c::{CloseHandle, BOOL, HANDLE};
446+
use crate::sys::cvt;
447+
448+
#[repr(C)]
449+
#[allow(non_snake_case)]
450+
struct PROCESSENTRY32W {
451+
dwSize: u32,
452+
cntUsage: u32,
453+
th32ProcessID: u32,
454+
th32DefaultHeapID: usize,
455+
th32ModuleID: u32,
456+
cntThreads: u32,
457+
th32ParentProcessID: u32,
458+
pcPriClassBase: i32,
459+
dwFlags: u32,
460+
szExeFile: [u16; 260],
461+
}
462+
463+
extern "system" {
464+
fn CreateToolhelp32Snapshot(dwflags: u32, th32processid: u32) -> HANDLE;
465+
fn Process32First(hsnapshot: HANDLE, lppe: *mut PROCESSENTRY32W) -> BOOL;
466+
fn Process32Next(hsnapshot: HANDLE, lppe: *mut PROCESSENTRY32W) -> BOOL;
467+
}
468+
469+
const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS: usize = 0x00020000;
470+
const TH32CS_SNAPPROCESS: u32 = 0x00000002;
471+
472+
struct ProcessDropGuard(crate::process::Child);
473+
474+
impl Drop for ProcessDropGuard {
475+
fn drop(&mut self) {
476+
let _ = self.0.kill();
477+
}
478+
}
479+
480+
let parent = ProcessDropGuard(Command::new("cmd").spawn().unwrap());
481+
482+
let mut child_cmd = Command::new("cmd");
483+
484+
unsafe {
485+
child_cmd
486+
.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.0.as_raw_handle() as isize);
487+
}
488+
489+
let child = ProcessDropGuard(child_cmd.spawn().unwrap());
490+
491+
let h_snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
492+
493+
let mut process_entry = PROCESSENTRY32W {
494+
dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
495+
cntUsage: 0,
496+
th32ProcessID: 0,
497+
th32DefaultHeapID: 0,
498+
th32ModuleID: 0,
499+
cntThreads: 0,
500+
th32ParentProcessID: 0,
501+
pcPriClassBase: 0,
502+
dwFlags: 0,
503+
szExeFile: [0; 260],
504+
};
505+
506+
unsafe { cvt(Process32First(h_snapshot, &mut process_entry as *mut _)) }.unwrap();
507+
508+
loop {
509+
if child.0.id() == process_entry.th32ProcessID {
510+
break;
511+
}
512+
unsafe { cvt(Process32Next(h_snapshot, &mut process_entry as *mut _)) }.unwrap();
513+
}
514+
515+
unsafe { cvt(CloseHandle(h_snapshot)) }.unwrap();
516+
517+
assert_eq!(parent.0.id(), process_entry.th32ParentProcessID);
518+
519+
drop(child)
520+
}
521+
437522
#[test]
438523
fn test_command_implements_send_sync() {
439524
fn take_send_sync_type<T: Send + Sync>(_: T) {}

library/std/src/sys/windows/c/windows_sys.lst

+5
Original file line numberDiff line numberDiff line change
@@ -2510,6 +2510,7 @@ Windows.Win32.System.Threading.CreateProcessW
25102510
Windows.Win32.System.Threading.CreateThread
25112511
Windows.Win32.System.Threading.DEBUG_ONLY_THIS_PROCESS
25122512
Windows.Win32.System.Threading.DEBUG_PROCESS
2513+
Windows.Win32.System.Threading.DeleteProcThreadAttributeList
25132514
Windows.Win32.System.Threading.DETACHED_PROCESS
25142515
Windows.Win32.System.Threading.ExitProcess
25152516
Windows.Win32.System.Threading.EXTENDED_STARTUPINFO_PRESENT
@@ -2524,8 +2525,10 @@ Windows.Win32.System.Threading.INFINITE
25242525
Windows.Win32.System.Threading.INHERIT_CALLER_PRIORITY
25252526
Windows.Win32.System.Threading.INHERIT_PARENT_AFFINITY
25262527
Windows.Win32.System.Threading.INIT_ONCE_INIT_FAILED
2528+
Windows.Win32.System.Threading.InitializeProcThreadAttributeList
25272529
Windows.Win32.System.Threading.InitOnceBeginInitialize
25282530
Windows.Win32.System.Threading.InitOnceComplete
2531+
Windows.Win32.System.Threading.LPPROC_THREAD_ATTRIBUTE_LIST
25292532
Windows.Win32.System.Threading.LPTHREAD_START_ROUTINE
25302533
Windows.Win32.System.Threading.NORMAL_PRIORITY_CLASS
25312534
Windows.Win32.System.Threading.OpenProcessToken
@@ -2561,6 +2564,7 @@ Windows.Win32.System.Threading.STARTF_USEPOSITION
25612564
Windows.Win32.System.Threading.STARTF_USESHOWWINDOW
25622565
Windows.Win32.System.Threading.STARTF_USESIZE
25632566
Windows.Win32.System.Threading.STARTF_USESTDHANDLES
2567+
Windows.Win32.System.Threading.STARTUPINFOEXW
25642568
Windows.Win32.System.Threading.STARTUPINFOW
25652569
Windows.Win32.System.Threading.STARTUPINFOW_FLAGS
25662570
Windows.Win32.System.Threading.SwitchToThread
@@ -2575,6 +2579,7 @@ Windows.Win32.System.Threading.TlsGetValue
25752579
Windows.Win32.System.Threading.TlsSetValue
25762580
Windows.Win32.System.Threading.TryAcquireSRWLockExclusive
25772581
Windows.Win32.System.Threading.TryAcquireSRWLockShared
2582+
Windows.Win32.System.Threading.UpdateProcThreadAttribute
25782583
Windows.Win32.System.Threading.WaitForMultipleObjects
25792584
Windows.Win32.System.Threading.WaitForSingleObject
25802585
Windows.Win32.System.Threading.WakeAllConditionVariable

library/std/src/sys/windows/c/windows_sys.rs

+37
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ extern "system" {
155155
pub fn DeleteFileW(lpfilename: PCWSTR) -> BOOL;
156156
}
157157
#[link(name = "kernel32")]
158+
extern "system" {
159+
pub fn DeleteProcThreadAttributeList(lpattributelist: LPPROC_THREAD_ATTRIBUTE_LIST) -> ();
160+
}
161+
#[link(name = "kernel32")]
158162
extern "system" {
159163
pub fn DeviceIoControl(
160164
hdevice: HANDLE,
@@ -371,6 +375,15 @@ extern "system" {
371375
) -> BOOL;
372376
}
373377
#[link(name = "kernel32")]
378+
extern "system" {
379+
pub fn InitializeProcThreadAttributeList(
380+
lpattributelist: LPPROC_THREAD_ATTRIBUTE_LIST,
381+
dwattributecount: u32,
382+
dwflags: u32,
383+
lpsize: *mut usize,
384+
) -> BOOL;
385+
}
386+
#[link(name = "kernel32")]
374387
extern "system" {
375388
pub fn MoveFileExW(
376389
lpexistingfilename: PCWSTR,
@@ -543,6 +556,18 @@ extern "system" {
543556
pub fn TryAcquireSRWLockShared(srwlock: *mut RTL_SRWLOCK) -> BOOLEAN;
544557
}
545558
#[link(name = "kernel32")]
559+
extern "system" {
560+
pub fn UpdateProcThreadAttribute(
561+
lpattributelist: LPPROC_THREAD_ATTRIBUTE_LIST,
562+
dwflags: u32,
563+
attribute: usize,
564+
lpvalue: *const ::core::ffi::c_void,
565+
cbsize: usize,
566+
lppreviousvalue: *mut ::core::ffi::c_void,
567+
lpreturnsize: *const usize,
568+
) -> BOOL;
569+
}
570+
#[link(name = "kernel32")]
546571
extern "system" {
547572
pub fn WaitForMultipleObjects(
548573
ncount: u32,
@@ -3567,6 +3592,7 @@ pub type LPOVERLAPPED_COMPLETION_ROUTINE = ::core::option::Option<
35673592
lpoverlapped: *mut OVERLAPPED,
35683593
) -> (),
35693594
>;
3595+
pub type LPPROC_THREAD_ATTRIBUTE_LIST = *mut ::core::ffi::c_void;
35703596
pub type LPPROGRESS_ROUTINE = ::core::option::Option<
35713597
unsafe extern "system" fn(
35723598
totalfilesize: i64,
@@ -3833,6 +3859,17 @@ pub const STARTF_USESHOWWINDOW: STARTUPINFOW_FLAGS = 1u32;
38333859
pub const STARTF_USESIZE: STARTUPINFOW_FLAGS = 2u32;
38343860
pub const STARTF_USESTDHANDLES: STARTUPINFOW_FLAGS = 256u32;
38353861
#[repr(C)]
3862+
pub struct STARTUPINFOEXW {
3863+
pub StartupInfo: STARTUPINFOW,
3864+
pub lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
3865+
}
3866+
impl ::core::marker::Copy for STARTUPINFOEXW {}
3867+
impl ::core::clone::Clone for STARTUPINFOEXW {
3868+
fn clone(&self) -> Self {
3869+
*self
3870+
}
3871+
}
3872+
#[repr(C)]
38363873
pub struct STARTUPINFOW {
38373874
pub cb: u32,
38383875
pub lpReserved: PWSTR,

0 commit comments

Comments
 (0)