-
Notifications
You must be signed in to change notification settings - Fork 13.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for Raw Pointers in windows_process_extensions_raw_attribute
Implementation
#121951
Conversation
rustbot has assigned @Mark-Simulacrum. Use r? to explicitly pick a reviewer |
I was not able to test this on Windows today because I did not have access to a machine, but I will try to do so tomorrow. |
.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.0.as_raw_handle() as isize); | ||
child_cmd.raw_attribute( | ||
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, | ||
parent.0.as_raw_handle(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will probably be necessary to change this to core::ptr::addr_of!(process_handle)
.
Where process_handle = parent.0.as_raw_handle()
.
/// child_cmd.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.as_raw_handle() as isize); | ||
/// child_cmd.raw_attribute( | ||
/// PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, | ||
/// parent.as_raw_handle(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here again, it will probably be necessary to change this to core::ptr::addr_of!(process_handle)
.
Where process_handle = parent.as_raw_handle()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. In the old code, it takes the value
argument, boxes it, and passes the box's data ptr as the lpValue
argument to UpdateProcThreadAttribute
. In the new code it directly passes value_ptr
through to UpdateProcThreadAttribute
. So an extra layer of reference needs to be added somewhere.
r? @ChrisDenton |
This is a big enough change in an (albeit unstable) API that it probably should have someone from libs-api giving it a sanity check. cc @dtolnay maybe? This changes a Windows-only extension to BOOL UpdateProcThreadAttribute(
[in, out] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
[in] DWORD dwFlags,
[in] DWORD_PTR Attribute,
[in] PVOID lpValue,
[in] SIZE_T cbSize,
// ...reserved parameters
) Previously an attempt at a safer (although still unsafe) wrapper was made by taking ownership of a value and then internally passing a pointer to it to the underlying API: unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>(&mut self, attribute: usize, value: T) However, this has certain problems. The most notable of which is that a newer attribute got clever and asks you to stuff an integer value into the pointer, which is not possible to do via this API. This PR changes it to take a raw pointer and size value, which more closely represents the underlying API: unsafe fn raw_attribute<T: 'static>(&mut self, attribute: usize, value_ptr: *const T, value_size: usize) This is more unsafe but I think it's the right call for now. If nothing else it allows for experimentation outside of std. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChrisDenton, this looks great to me. Feel free to approve (with the addr_of
changes that Michael has already flagged).
Thank you for the writeup of the underlying Windows API, the current Rust way it's exposed, and the problem that's precipitating changing it.
) { | ||
self.proc_thread_attributes.insert( | ||
attribute, | ||
ProcThreadAttributeValue { size: mem::size_of::<T>(), data: Box::new(value) }, | ||
ProcThreadAttributeValue { ptr: mem::transmute::<*const T, *const c_void>(ptr), size }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ProcThreadAttributeValue { ptr: mem::transmute::<*const T, *const c_void>(ptr), size }, | |
ProcThreadAttributeValue { ptr: ptr.cast::<c_void>(), size }, |
/// child_cmd.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.as_raw_handle() as isize); | ||
/// child_cmd.raw_attribute( | ||
/// PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, | ||
/// parent.as_raw_handle(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. In the old code, it takes the value
argument, boxes it, and passes the box's data ptr as the lpValue
argument to UpdateProcThreadAttribute
. In the new code it directly passes value_ptr
through to UpdateProcThreadAttribute
. So an extra layer of reference needs to be added somewhere.
unsafe fn raw_attribute<T: 'static>( | ||
&mut self, | ||
attribute: usize, | ||
value: T, | ||
value_ptr: *const T, | ||
value_size: usize, | ||
) -> &mut process::Command; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the current thinking is to stabilize raw_attribute
without any of the reserved parameters of UpdateProcThreadAttribute
exposed? dwFlags
, lpPreviousValue
, lpReturnSize
. That seems fine to me, but can you predict how we'd react if those started being used by Windows? A CommandExt::raw_attribute_with_flags
, for example? Or a callback kind of thing like unix's CommandExt::pre_exec
, but where it gives you access to the lpAttributeList
so that you can "do it yourself" with any arbitrary call to UpdateProcThreadAttribute
?
/// The `attribute` parameter specifies the raw attribute to be set, while the `value_ptr` parameter holds the pointer to the value associated with that attribute, | ||
/// and the `value_size` parameter indicates the size of the value in bytes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am confused about the required lifetime for value_ptr
.
In the large example code block at the bottom of https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute#remarks, it demonstrates a correct UpdateProcThreadAttribute
call. Roughly:
DWORD ProtectionLevel = ...;
UpdateProcThreadAttribute(...,
&ProtectionLevel,
sizeof(ProtectionLevel))
CreateProcessW(...)
The documentation for lpValue
says (emphasis in the original):
A pointer to the attribute value. This value must persist until the attribute list is destroyed using the DeleteProcThreadAttributeList function.
Where does the DeleteProcThreadAttributeList
call occur? Such a thing is not visible in the example, and it's not mentioned anywhere else on that page.
Jumping to conclusions, I assume that CreateProcessW
calls DeleteProcThreadAttributeList
, i.e. *lpValue
(not just **lpValue
) needs to remain usable not just for the duration of the UpdateProcThreadAttribute
call, but also after it.
If I'm right about that, I think this needs to be made a lot more clear in this Rust documentation.
It wasn't an issue before because the standard library itself would heap-allocate your value for you and keep it until after spawn
or whatever else does the CreateProcessW
/ DeleteProcThreadAttributeList
.
If it's now the caller's responsibility, that needs to be a lot more clear with a better example and a discussion of the pitfall of doing .raw_attribute(..., &parent.as_raw_handle() as *const _);
with a temporary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does the DeleteProcThreadAttributeList call occur? It's not visible in the example, nor is it mentioned anywhere else on that page.
It is currently invoked as soon as the ProcThreadAttributeList
is dropped at the end of the spawn call.
But you bring up a valid point that was also discussed in the original PR.
Jumping to conclusions, I assume that CreateProcessW calls DeleteProcThreadAttributeList.
Actually, no. You can use the same ProcThreadAttributeList
to spawn multiple processes. Perhaps it would be better to offload the entire struct and pass a reference to it during the spawn call, something like spawn_with_attributes
. This way, we could tie a lifetime to the new struct with the minimum lifetime passed through the raw_attribute
interface.
I will try to write something up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I in vision something like this:
struct ProcThreadAttributeListBuilder<'a> {
values: Vec<(usize, (*const c_void, usize))>,
_lifetime_marker: PhantomData<fn() -> &'a ()>,
}
impl<'a> ProcThreadAttributeListBuilder<'a> {
fn new() -> Self {
ProcThreadAttributeListBuilder {
values: vec![],
_lifetime_marker: PhantomData,
}
}
fn attribute<T>(&mut self, attribute: usize, value: &'a T) {
unsafe {
self.raw_attribute(attribute, addr_of!(*value).cast::<c_void>(), size_of::<T>());
}
}
unsafe fn raw_attribute<T>(&mut self, attribute: usize, value_ptr: *const T, value_size: usize) {
self.values.push((attribute, (value_ptr.cast::<c_void>(), value_size)));
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a builder pattern here because the length of the attribute list is immutable as soon as it is created.
We might get away with pre-allocating it and then reallocating as soon as the attribute count extends the allocated, but I don't know if windows would like that.
I've gone ahead and abstracted away the implementation into its own struct michaelvanstraten@4134d3e, let me know if I should abandon this PR in favor. |
Closing this as it's superseded by #123604 |
As extensively discussed in issue #114854, the current implementation of the unstable
windows_process_extensions_raw_attribute
features lacks support for passing a raw pointer.This PR addresses this issue by allowing the passing of raw pointer types to the
std::os::windows::CommandExt::raw_attribute
method, which is directly handed to theUpdateProcThreadAttribute
Win32 API call.This change does introduce some concerns with the necessity to pass a raw pointer, but considering this should be treated as a raw interface to set
ProcThreadAttributes
, there is no real alternative besides drastically increasing the complexity of the interface.