Skip to content

Commit 484c405

Browse files
committed
Auto merge of rust-lang#123604 - michaelvanstraten:proc_thread_attribute_list, r=<try>
Abstract `ProcThreadAttributeList` into its own struct As extensively discussed in issue rust-lang#114854, the current implementation of the unstable `windows_process_extensions_raw_attribute` features lacks support for passing a raw pointer. This PR wants to explore the opportunity to abstract away the `ProcThreadAttributeList` into its own struct to for one improve safety and usability and secondly make it possible to maybe also use it to spawn new threads. try-job: x86_64-msvc
2 parents 1f44f0a + f9fb568 commit 484c405

File tree

3 files changed

+278
-151
lines changed

3 files changed

+278
-151
lines changed

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

+257-55
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
55
#![stable(feature = "process_extensions", since = "1.2.0")]
66

7-
use crate::ffi::OsStr;
7+
use crate::ffi::{c_void, OsStr};
8+
use crate::mem::MaybeUninit;
89
use crate::os::windows::io::{
910
AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle,
1011
};
1112
use crate::sealed::Sealed;
1213
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
13-
use crate::{process, sys};
14+
use crate::{io, marker, process, ptr, sys};
1415

1516
#[stable(feature = "process_extensions", since = "1.2.0")]
1617
impl FromRawHandle for process::Stdio {
@@ -295,41 +296,25 @@ pub trait CommandExt: Sealed {
295296
#[unstable(feature = "windows_process_extensions_async_pipes", issue = "98289")]
296297
fn async_pipes(&mut self, always_async: bool) -> &mut process::Command;
297298

298-
/// Set a raw attribute on the command, providing extended configuration options for Windows
299-
/// processes.
299+
/// Executes the command as a child process with the given
300+
/// [`ProcThreadAttributeList`], returning a handle to it.
300301
///
301-
/// This method allows you to specify custom attributes for a child process on Windows systems
302-
/// using raw attribute values. Raw attributes provide extended configurability for process
303-
/// creation, but their usage can be complex and potentially unsafe.
304-
///
305-
/// The `attribute` parameter specifies the raw attribute to be set, while the `value`
306-
/// parameter holds the value associated with that attribute. Please refer to the
307-
/// [`windows-rs` documentation] or the [Win32 API documentation] for detailed information
308-
/// about available attributes and their meanings.
309-
///
310-
/// [`windows-rs` documentation]: https://microsoft.github.io/windows-docs-rs/doc/windows/
311-
/// [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute
302+
/// This method enables the customization of attributes for the spawned
303+
/// child process on Windows systems.
304+
/// Attributes offer extended configurability for process creation,
305+
/// but their usage can be intricate and potentially unsafe.
312306
///
313307
/// # Note
314308
///
315-
/// The maximum number of raw attributes is the value of [`u32::MAX`].
316-
/// If this limit is exceeded, the call to [`process::Command::spawn`] will return an `Error`
317-
/// indicating that the maximum number of attributes has been exceeded.
318-
///
319-
/// # Safety
320-
///
321-
/// The usage of raw attributes is potentially unsafe and should be done with caution.
322-
/// Incorrect attribute values or improper configuration can lead to unexpected behavior or
323-
/// errors.
309+
/// By default, stdin, stdout, and stderr are inherited from the parent
310+
/// process.
324311
///
325312
/// # Example
326313
///
327-
/// The following example demonstrates how to create a child process with a specific parent
328-
/// process ID using a raw attribute.
329-
///
330-
/// ```rust
314+
/// ```
331315
/// #![feature(windows_process_extensions_raw_attribute)]
332-
/// use std::os::windows::{process::CommandExt, io::AsRawHandle};
316+
/// use std::os::windows::process::CommandExt;
317+
/// use std::os::windows::io::AsRawHandle;
333318
/// use std::process::Command;
334319
///
335320
/// # struct ProcessDropGuard(std::process::Child);
@@ -338,36 +323,27 @@ pub trait CommandExt: Sealed {
338323
/// # let _ = self.0.kill();
339324
/// # }
340325
/// # }
341-
///
326+
/// #
342327
/// let parent = Command::new("cmd").spawn()?;
343-
///
344-
/// let mut child_cmd = Command::new("cmd");
328+
/// let parent_process_handle = parent.as_raw_handle();
329+
/// # let parent = ProcessDropGuard(parent);
345330
///
346331
/// const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS: usize = 0x00020000;
332+
/// let mut attribute_list = ProcThreadAttributeList::build()
333+
/// .attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent_process_handle)
334+
/// .finish()
335+
/// .unwrap();
347336
///
348-
/// unsafe {
349-
/// child_cmd.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.as_raw_handle() as isize);
350-
/// }
337+
/// let mut child = Command::new("cmd").spawn_with_attributes(attribute_list)?;
351338
/// #
352-
/// # let parent = ProcessDropGuard(parent);
353-
///
354-
/// let mut child = child_cmd.spawn()?;
355-
///
356339
/// # child.kill()?;
357340
/// # Ok::<(), std::io::Error>(())
358341
/// ```
359-
///
360-
/// # Safety Note
361-
///
362-
/// Remember that improper use of raw attributes can lead to undefined behavior or security
363-
/// vulnerabilities. Always consult the documentation and ensure proper attribute values are
364-
/// used.
365342
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
366-
unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>(
343+
fn spawn_with_attributes(
367344
&mut self,
368-
attribute: usize,
369-
value: T,
370-
) -> &mut process::Command;
345+
attribute_list: &ProcThreadAttributeList<'_>,
346+
) -> io::Result<process::Child>;
371347
}
372348

373349
#[stable(feature = "windows_process_extensions", since = "1.16.0")]
@@ -401,13 +377,13 @@ impl CommandExt for process::Command {
401377
self
402378
}
403379

404-
unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>(
380+
fn spawn_with_attributes(
405381
&mut self,
406-
attribute: usize,
407-
value: T,
408-
) -> &mut process::Command {
409-
unsafe { self.as_inner_mut().raw_attribute(attribute, value) };
410-
self
382+
attribute_list: &ProcThreadAttributeList<'_>,
383+
) -> io::Result<process::Child> {
384+
self.as_inner_mut()
385+
.spawn_with_attributes(sys::process::Stdio::Inherit, true, Some(attribute_list))
386+
.map(process::Child::from_inner)
411387
}
412388
}
413389

@@ -447,3 +423,229 @@ impl ExitCodeExt for process::ExitCode {
447423
process::ExitCode::from_inner(From::from(raw))
448424
}
449425
}
426+
427+
/// A wrapper around windows [`ProcThreadAttributeList`][1].
428+
///
429+
/// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist>
430+
#[derive(Debug)]
431+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
432+
pub struct ProcThreadAttributeList<'a> {
433+
attribute_list: Box<[MaybeUninit<u8>]>,
434+
_lifetime_marker: marker::PhantomData<&'a ()>,
435+
}
436+
437+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
438+
impl<'a> ProcThreadAttributeList<'a> {
439+
/// Creates a new builder for constructing a [`ProcThreadAttributeList`].
440+
pub fn build() -> ProcThreadAttributeListBuilder<'a> {
441+
ProcThreadAttributeListBuilder::new()
442+
}
443+
444+
/// Returns a pointer to the underling attribute list.
445+
#[doc(hidden)]
446+
pub fn as_ptr(&self) -> *const MaybeUninit<u8> {
447+
self.attribute_list.as_ptr()
448+
}
449+
}
450+
451+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
452+
impl<'a> Drop for ProcThreadAttributeList<'a> {
453+
/// Deletes the attribute list.
454+
///
455+
/// This method calls [`DeleteProcThreadAttributeList`][1] to delete the
456+
/// underlying attribute list.
457+
///
458+
/// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-deleteprocthreadattributelist>
459+
fn drop(&mut self) {
460+
let lp_attribute_list = self.attribute_list.as_mut_ptr().cast::<c_void>();
461+
unsafe { sys::c::DeleteProcThreadAttributeList(lp_attribute_list) }
462+
}
463+
}
464+
465+
/// Builder for constructing a [`ProcThreadAttributeList`].
466+
#[derive(Clone, Debug)]
467+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
468+
pub struct ProcThreadAttributeListBuilder<'a> {
469+
attributes: alloc::collections::BTreeMap<usize, ProcThreadAttributeValue>,
470+
_lifetime_marker: marker::PhantomData<&'a ()>,
471+
}
472+
473+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
474+
impl<'a> ProcThreadAttributeListBuilder<'a> {
475+
fn new() -> Self {
476+
ProcThreadAttributeListBuilder {
477+
attributes: alloc::collections::BTreeMap::new(),
478+
_lifetime_marker: marker::PhantomData,
479+
}
480+
}
481+
482+
/// Sets an attribute on the attribute list.
483+
///
484+
/// The `attribute` parameter specifies the raw attribute to be set, while
485+
/// the `value` parameter holds the value associated with that attribute.
486+
/// Please refer to the [Windows documentation][1] for a list of valid attributes.
487+
///
488+
/// # Note
489+
///
490+
/// The maximum number of attributes is the value of [`u32::MAX`]. If this
491+
/// limit is exceeded, the call to [`Self::finish`] will return an `Error`
492+
/// indicating that the maximum number of attributes has been exceeded.
493+
///
494+
/// # Safety Note
495+
///
496+
/// Remember that improper use of attributes can lead to undefined behavior
497+
/// or security vulnerabilities. Always consult the documentation and ensure
498+
/// proper attribute values are used.
499+
///
500+
/// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute#parameters>
501+
pub fn attribute<T>(self, attribute: usize, value: &'a T) -> Self {
502+
unsafe {
503+
self.raw_attribute(
504+
attribute,
505+
ptr::addr_of!(*value).cast::<c_void>(),
506+
crate::mem::size_of::<T>(),
507+
)
508+
}
509+
}
510+
511+
/// Sets a raw attribute on the attribute list.
512+
///
513+
/// This function is useful for setting attributes with pointers or sizes
514+
/// that cannot be derived directly from their values.
515+
///
516+
/// # Safety
517+
///
518+
/// This function is marked as `unsafe` because it deals with raw pointers
519+
/// and sizes. It is the responsibility of the caller to ensure the value
520+
/// lives longer than the [`ProcThreadAttributeListBuilder`] as well as
521+
/// the validity of the size parameter.
522+
///
523+
/// # Example
524+
/// ```
525+
/// use std::mem;
526+
/// use std::os::windows::raw::HANDLE;
527+
///
528+
/// #[repr(C)]
529+
/// pub struct COORD {
530+
/// pub X: i16,
531+
/// pub Y: i16,
532+
/// }
533+
///
534+
/// extern "system" {
535+
/// fn CreatePipe(hreadpipe: HANDLE, hwritepipe: HANDLE, lppipeattributes: *const c_void, nsize: u32) -> i32;
536+
/// fn CreatePseudoConsole(size: COORD, hinput: isize, houtput: isize, dwflags: u32, phpc: HANDLE) -> i32;
537+
/// fn CloseHandle(hobject: isize) -> i32;
538+
/// }
539+
///
540+
/// let (mut input_read_side, mut output_write_side) = unsafe { (zeroed(), zeroed()) };
541+
/// let (mut output_read_side, mut input_write_side) = unsafe { (zeroed(), zeroed()) };
542+
///
543+
/// unsafe {
544+
/// CreatePipe(&mut input_read_side, &mut input_write_side, None, 0);
545+
/// CreatePipe(&mut output_read_side, &mut output_write_side, None, 0);
546+
/// }
547+
///
548+
/// let size = COORD { X: 60, Y: 40 };
549+
/// let h_pc = unsafe { CreatePseudoConsole(size, input_read_side, output_write_side, 0) };
550+
///
551+
/// unsafe { CloseHandle(input_read_side) };
552+
/// unsafe { CloseHandle(output_write_side) };
553+
///
554+
/// const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE: u32 = 131094u32;
555+
///
556+
/// let attribute_list = unsafe {
557+
/// ProcThreadAttributeList::build()
558+
/// .raw_attribute(
559+
/// PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
560+
/// h_pc.cast::<HANDLE>,
561+
/// mem::size_of::<HANDLE>()
562+
/// )
563+
/// .finish()
564+
/// };
565+
///
566+
/// let child = Command::new("cmd").spawn_with_attributes(attribute_list)?;
567+
/// #
568+
/// # child.kill()?;
569+
/// # Ok::<(), std::io::Error>(())
570+
/// ```
571+
pub unsafe fn raw_attribute<T>(
572+
mut self,
573+
attribute: usize,
574+
value_ptr: *const T,
575+
value_size: usize,
576+
) -> Self {
577+
self.attributes.insert(
578+
attribute,
579+
ProcThreadAttributeValue { ptr: value_ptr.cast::<c_void>(), size: value_size },
580+
);
581+
self
582+
}
583+
584+
/// Finalizes the construction of the `ProcThreadAttributeList`.
585+
///
586+
/// # Errors
587+
///
588+
/// Returns an error if the maximum number of attributes is exceeded
589+
/// or if there is an I/O error during initialization.
590+
pub fn finish(&self) -> io::Result<ProcThreadAttributeList<'a>> {
591+
// To initialize our ProcThreadAttributeList, we need to determine
592+
// how many bytes to allocate for it. The Windows API simplifies this
593+
// process by allowing us to call `InitializeProcThreadAttributeList`
594+
// with a null pointer to retrieve the required size.
595+
let mut required_size = 0;
596+
let Ok(attribute_count) = self.attributes.len().try_into() else {
597+
return Err(io::const_io_error!(
598+
io::ErrorKind::InvalidInput,
599+
"maximum number of ProcThreadAttributes exceeded",
600+
));
601+
};
602+
unsafe {
603+
sys::c::InitializeProcThreadAttributeList(
604+
ptr::null_mut(),
605+
attribute_count,
606+
0,
607+
&mut required_size,
608+
)
609+
};
610+
611+
let mut attribute_list = vec![MaybeUninit::uninit(); required_size].into_boxed_slice();
612+
613+
// Once we've allocated the necessary memory, it's safe to invoke
614+
// `InitializeProcThreadAttributeList` to properly initialize the list.
615+
sys::cvt(unsafe {
616+
sys::c::InitializeProcThreadAttributeList(
617+
attribute_list.as_mut_ptr().cast::<c_void>(),
618+
attribute_count,
619+
0,
620+
&mut required_size,
621+
)
622+
})?;
623+
624+
// # Add our attributes to the buffer.
625+
// It's theoretically possible for the attribute count to exceed a u32
626+
// value. Therefore, we ensure that we don't add more attributes than
627+
// the buffer was initialized for.
628+
for (&attribute, value) in self.attributes.iter().take(attribute_count as usize) {
629+
sys::cvt(unsafe {
630+
sys::c::UpdateProcThreadAttribute(
631+
attribute_list.as_mut_ptr().cast::<c_void>(),
632+
0,
633+
attribute,
634+
value.ptr,
635+
value.size,
636+
ptr::null_mut(),
637+
ptr::null_mut(),
638+
)
639+
})?;
640+
}
641+
642+
Ok(ProcThreadAttributeList { attribute_list, _lifetime_marker: marker::PhantomData })
643+
}
644+
}
645+
646+
/// Wrapper around the value data to be used as a Process Thread Attribute.
647+
#[derive(Clone, Debug)]
648+
struct ProcThreadAttributeValue {
649+
ptr: *const c_void,
650+
size: usize,
651+
}

library/std/src/process/tests.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ fn test_creation_flags() {
436436
fn test_proc_thread_attributes() {
437437
use crate::mem;
438438
use crate::os::windows::io::AsRawHandle;
439-
use crate::os::windows::process::CommandExt;
439+
use crate::os::windows::process::{CommandExt, ProcThreadAttributeList};
440440
use crate::sys::c::{CloseHandle, BOOL, HANDLE};
441441
use crate::sys::cvt;
442442

@@ -476,12 +476,14 @@ fn test_proc_thread_attributes() {
476476

477477
let mut child_cmd = Command::new("cmd");
478478

479-
unsafe {
480-
child_cmd
481-
.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.0.as_raw_handle() as isize);
482-
}
479+
let parent_process_handle = parent.0.as_raw_handle();
480+
481+
let mut attribute_list = ProcThreadAttributeList::build()
482+
.attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent_process_handle)
483+
.finish()
484+
.unwrap();
483485

484-
let child = ProcessDropGuard(child_cmd.spawn().unwrap());
486+
let child = ProcessDropGuard(child_cmd.spawn_with_attributes(&mut attribute_list).unwrap());
485487

486488
let h_snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
487489

0 commit comments

Comments
 (0)