windows_namespace_request_sys/handle.rs
1// Copyright (c) Mike Grier.
2
3//! Owned handle references.
4//!
5//! Five of the round-one entries take a handle rather than a path, so handle
6//! ownership is a shared primitive rather than a detail of any one of them.
7//! [`CapturedHandle`] is that primitive: it duplicates a caller's handle at
8//! capture, owns the duplicate for its life, and closes it on drop.
9
10use std::ffi::c_void;
11use std::fmt;
12use std::io;
13use std::os::windows::io::{
14 AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle, RawHandle,
15};
16use std::ptr;
17
18use windows_sys::Win32::Foundation::{
19 DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_INVALID_HANDLE, FALSE, HANDLE,
20};
21use windows_sys::Win32::System::Threading::GetCurrentProcess;
22
23/// The handle values Windows reserves for pseudo-handles, as named constants
24/// rather than bare integers.
25///
26/// A pseudo-handle is not a reference to a kernel object; it is a constant that
27/// the calling thread resolves against *itself* at each use. Changing any value
28/// here is a breaking change.
29mod pseudo {
30 /// `GetCurrentProcess`, and also `INVALID_HANDLE_VALUE`.
31 pub const CURRENT_PROCESS: isize = -1;
32 /// `GetCurrentThread`.
33 pub const CURRENT_THREAD: isize = -2;
34 /// Reserved by Windows; no documented producer.
35 pub const RESERVED: isize = -3;
36 /// `GetCurrentProcessToken`.
37 pub const CURRENT_PROCESS_TOKEN: isize = -4;
38 /// `GetCurrentThreadToken`.
39 pub const CURRENT_THREAD_TOKEN: isize = -5;
40 /// `GetCurrentThreadEffectiveToken`.
41 pub const CURRENT_THREAD_EFFECTIVE_TOKEN: isize = -6;
42}
43
44/// Why a handle could not be captured.
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
46#[non_exhaustive]
47pub enum HandleCaptureFailure {
48 /// The source handle was null.
49 NullHandle,
50 /// The source handle was `INVALID_HANDLE_VALUE`.
51 ///
52 /// Rejected explicitly rather than passed through, because
53 /// `INVALID_HANDLE_VALUE` is *also* the current-process pseudo-handle:
54 /// `DuplicateHandle` would accept it and hand back a perfectly valid handle
55 /// to the current process, so an unchecked `CreateFileW` failure would be
56 /// captured as a successful open of something else entirely.
57 InvalidHandleValue,
58 /// The source handle was one of the other Win32 pseudo-handles.
59 ///
60 /// A pseudo-handle names whatever the *using* thread is, so duplicating one
61 /// on the caller's thread and using the result on a worker would silently
62 /// change what it refers to.
63 PseudoHandle,
64 /// Windows refused to duplicate the source handle.
65 ///
66 /// A handle that has already been closed fails here, with
67 /// `ERROR_INVALID_HANDLE`.
68 DuplicateHandle,
69 /// The security attributes a request owns could not be copied, because the memory for the
70 /// copy could not be allocated.
71 ///
72 /// Reported by a request's `try_clone`, which copies them along with any handle it owns.
73 CopySecurity,
74}
75
76/// A synchronous failure while capturing a caller's handle.
77///
78/// Duplication failure is a **construction** error by design: it is raised on
79/// the calling thread, at the point the request is built, where the caller still
80/// holds the source handle and can still do something about it. Deferring it to
81/// execution would report a caller's mistake on a worker, to code that has no
82/// way to correct it.
83#[derive(Debug)]
84pub struct HandleCaptureError {
85 failure: HandleCaptureFailure,
86 source: io::Error,
87}
88
89impl HandleCaptureError {
90 fn new(failure: HandleCaptureFailure, source: io::Error) -> Self {
91 Self { failure, source }
92 }
93
94 /// A request's security attributes could not be copied; `source` says why.
95 pub(crate) fn copying_security(source: io::Error) -> Self {
96 Self::new(HandleCaptureFailure::CopySecurity, source)
97 }
98
99 fn invalid_handle(failure: HandleCaptureFailure) -> Self {
100 Self::new(
101 failure,
102 io::Error::from_raw_os_error(
103 i32::try_from(ERROR_INVALID_HANDLE).expect("ERROR_INVALID_HANDLE fits in i32"),
104 ),
105 )
106 }
107
108 /// Why the capture failed.
109 #[must_use]
110 pub fn failure(&self) -> HandleCaptureFailure {
111 self.failure
112 }
113
114 /// The underlying Win32 error code.
115 #[must_use]
116 pub fn raw_os_error(&self) -> Option<i32> {
117 self.source.raw_os_error()
118 }
119}
120
121impl fmt::Display for HandleCaptureError {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 let stage = match self.failure {
124 HandleCaptureFailure::NullHandle => "null source handle",
125 HandleCaptureFailure::InvalidHandleValue => "INVALID_HANDLE_VALUE source handle",
126 HandleCaptureFailure::PseudoHandle => "pseudo-handle source handle",
127 HandleCaptureFailure::DuplicateHandle => "DuplicateHandle",
128 HandleCaptureFailure::CopySecurity => "copying the security attributes",
129 };
130
131 write!(f, "{stage}: {}", self.source)
132 }
133}
134
135impl std::error::Error for HandleCaptureError {
136 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
137 Some(&self.source)
138 }
139}
140
141/// An owned duplicate of a handle a request names.
142///
143/// # A path is copied; a handle is duplicated
144///
145/// This is the distinction a caller reasoning in value semantics will get
146/// wrong, so it is stated rather than implied: **a path is a value and is
147/// copied; a handle is a reference to a kernel object, and duplicating it
148/// shares that object rather than cloning it.**
149///
150/// A request holding a `CapturedHandle` is therefore self-contained with
151/// respect to **lifetime** -- it cannot be left pointing at a handle its
152/// originator closed, because it holds its own reference and closes it on drop
153/// -- and is **not** isolated with respect to **state**. Measured, not reasoned:
154///
155/// - A duplicate **shares directory-enumeration state**: it continues where the
156/// source stopped rather than starting its own listing. An independent
157/// traversal needs a fresh open, not a duplicate.
158/// - Closing the duplicate **does not disturb the source**. This is what makes
159/// the whole design safe: a request may own a duplicate and drop it without
160/// damaging the handle its caller kept.
161/// - Single-shot metadata queries disturb nothing, on the source or on a
162/// duplicate.
163///
164/// # What is duplicated
165///
166/// The duplicate carries the source's access rights (`DUPLICATE_SAME_ACCESS`),
167/// because a request must be able to perform exactly the call the caller opened
168/// the handle for. It is **not inheritable**, so capturing a handle never
169/// widens what a child process can reach.
170///
171/// # Example
172///
173/// The lifetime guarantee, which is the reason this type exists: the capture
174/// keeps working after its source is gone.
175///
176/// ```
177/// use std::fs;
178/// use std::os::windows::io::AsHandle;
179///
180/// use windows_namespace_request_sys::CapturedHandle;
181///
182/// let path = std::env::temp_dir().join(format!("wnrs-dup-{}.tmp", std::process::id()));
183/// fs::write(&path, b"seven..")?;
184///
185/// let captured = {
186/// let file = fs::File::open(&path)?;
187/// CapturedHandle::capture(file.as_handle())?
188/// // `file` is closed here; the duplicate is not.
189/// };
190///
191/// let adopted = fs::File::from(captured.into_owned_handle());
192/// assert_eq!(adopted.metadata()?.len(), 7);
193/// # drop(adopted);
194/// # fs::remove_file(&path)?;
195/// # Ok::<(), Box<dyn std::error::Error>>(())
196/// ```
197///
198/// # Example: what a duplicate shares
199///
200/// The distinction a caller reasoning in value semantics gets backwards.
201/// Closing the duplicate leaves the source perfectly usable, because both are
202/// references to one kernel object rather than two copies of it:
203///
204/// ```
205/// use std::fs;
206/// use std::os::windows::io::AsHandle;
207///
208/// use windows_namespace_request_sys::CapturedHandle;
209///
210/// let path = std::env::temp_dir().join(format!("wnrs-dup2-{}.tmp", std::process::id()));
211/// fs::write(&path, b"seven..")?;
212/// let file = fs::File::open(&path)?;
213///
214/// let captured = CapturedHandle::capture(file.as_handle())?;
215/// drop(captured);
216///
217/// // The source is untouched -- which is what makes it safe for a request to
218/// // own a duplicate and drop it.
219/// assert_eq!(file.metadata()?.len(), 7);
220/// # drop(file);
221/// # fs::remove_file(&path)?;
222/// # Ok::<(), Box<dyn std::error::Error>>(())
223/// ```
224#[derive(Debug)]
225#[must_use = "dropping the captured handle closes the duplicate"]
226pub struct CapturedHandle {
227 duplicate: OwnedHandle,
228}
229
230impl CapturedHandle {
231 /// Captures `source` by duplicating it into this process.
232 ///
233 /// # Errors
234 ///
235 /// Returns a [`HandleCaptureError`] when `source` is null, is
236 /// `INVALID_HANDLE_VALUE`, is a Win32 pseudo-handle, or cannot be
237 /// duplicated -- which is what an already-closed handle produces.
238 ///
239 /// # Example
240 ///
241 /// ```
242 /// use std::fs;
243 /// use std::os::windows::io::{AsHandle, AsRawHandle};
244 ///
245 /// use windows_namespace_request_sys::CapturedHandle;
246 ///
247 /// let path = std::env::temp_dir().join(format!("wnrs-cap-{}.tmp", std::process::id()));
248 /// fs::write(&path, b"x")?;
249 /// let file = fs::File::open(&path)?;
250 ///
251 /// let captured = CapturedHandle::capture(file.as_handle())?;
252 ///
253 /// // A duplicate is a second reference, so it has its own handle value.
254 /// assert_ne!(captured.as_handle().as_raw_handle(), file.as_raw_handle());
255 /// # drop(file);
256 /// # drop(captured);
257 /// # fs::remove_file(&path)?;
258 /// # Ok::<(), Box<dyn std::error::Error>>(())
259 /// ```
260 ///
261 /// # Example: the failure that would otherwise be silent
262 ///
263 /// `INVALID_HANDLE_VALUE` and the current-process pseudo-handle are the
264 /// same value, so an unchecked `CreateFileW` failure passed to
265 /// `DuplicateHandle` would *succeed* and yield a process handle. Capture
266 /// refuses it by name:
267 ///
268 /// ```
269 /// use windows_namespace_request_sys::handle::HandleCaptureFailure;
270 /// use windows_namespace_request_sys::CapturedHandle;
271 /// use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
272 ///
273 /// // SAFETY: the value is validated, never dereferenced.
274 /// let error = unsafe { CapturedHandle::capture_raw(INVALID_HANDLE_VALUE) }
275 /// .expect_err("INVALID_HANDLE_VALUE is never a real handle");
276 ///
277 /// assert_eq!(error.failure(), HandleCaptureFailure::InvalidHandleValue);
278 /// ```
279 pub fn capture(source: BorrowedHandle<'_>) -> Result<Self, HandleCaptureError> {
280 // SAFETY: BorrowedHandle's invariant is that the handle it names stays
281 // open for its borrow, which covers this call.
282 unsafe { Self::capture_raw(source.as_raw_handle()) }
283 }
284
285 /// Captures a raw handle by duplicating it into this process.
286 ///
287 /// Prefer [`capture`](Self::capture) where an owned or borrowed handle is
288 /// available. This form exists for the common case of a raw `HANDLE` that
289 /// came straight back from a Win32 call and has no Rust owner yet.
290 ///
291 /// # Errors
292 ///
293 /// As [`capture`](Self::capture).
294 ///
295 /// # Safety
296 ///
297 /// `source` must remain open for the duration of this call. A handle closed
298 /// concurrently may have had its value reused by another thread, in which
299 /// case this captures a different kernel object rather than failing.
300 pub unsafe fn capture_raw(source: RawHandle) -> Result<Self, HandleCaptureError> {
301 if source.is_null() {
302 return Err(HandleCaptureError::invalid_handle(
303 HandleCaptureFailure::NullHandle,
304 ));
305 }
306
307 match source as isize {
308 pseudo::CURRENT_PROCESS => {
309 return Err(HandleCaptureError::invalid_handle(
310 HandleCaptureFailure::InvalidHandleValue,
311 ));
312 }
313 pseudo::CURRENT_THREAD
314 | pseudo::RESERVED
315 | pseudo::CURRENT_PROCESS_TOKEN
316 | pseudo::CURRENT_THREAD_TOKEN
317 | pseudo::CURRENT_THREAD_EFFECTIVE_TOKEN => {
318 return Err(HandleCaptureError::invalid_handle(
319 HandleCaptureFailure::PseudoHandle,
320 ));
321 }
322 _ => {}
323 }
324
325 let mut duplicate: HANDLE = ptr::null_mut();
326
327 // SAFETY: GetCurrentProcess returns the current-process pseudo-handle,
328 // which is exactly what DuplicateHandle wants for a same-process
329 // duplication; source is a live handle per this function's contract;
330 // duplicate points to writable storage. FALSE makes the duplicate
331 // non-inheritable, and DUPLICATE_SAME_ACCESS makes the desired-access
332 // argument ignored.
333 let duplicated = unsafe {
334 let process = GetCurrentProcess();
335 DuplicateHandle(
336 process,
337 source,
338 process,
339 &raw mut duplicate,
340 0,
341 FALSE,
342 DUPLICATE_SAME_ACCESS,
343 )
344 };
345 if duplicated == FALSE {
346 return Err(HandleCaptureError::new(
347 HandleCaptureFailure::DuplicateHandle,
348 io::Error::last_os_error(),
349 ));
350 }
351
352 // SAFETY: a successful DuplicateHandle yields a new handle that this
353 // process must release with CloseHandle, which OwnedHandle does.
354 let duplicate = unsafe { OwnedHandle::from_raw_handle(duplicate) };
355 Ok(Self { duplicate })
356 }
357
358 /// Captures a second, independently owned duplicate.
359 ///
360 /// This is not `Clone` because duplication is fallible. The result refers to
361 /// the *same* kernel object, with everything that implies above.
362 ///
363 /// # Errors
364 ///
365 /// As [`capture`](Self::capture), though only
366 /// [`HandleCaptureFailure::DuplicateHandle`] is reachable: the value being
367 /// duplicated is already known to be a real, open handle.
368 ///
369 /// # Example
370 ///
371 /// ```
372 /// use std::fs;
373 /// use std::os::windows::io::{AsHandle, AsRawHandle};
374 ///
375 /// use windows_namespace_request_sys::CapturedHandle;
376 ///
377 /// let path = std::env::temp_dir().join(format!("wnrs-clone-{}.tmp", std::process::id()));
378 /// fs::write(&path, b"x")?;
379 /// let file = fs::File::open(&path)?;
380 /// let first = CapturedHandle::capture(file.as_handle())?;
381 ///
382 /// let second = first.try_clone()?;
383 ///
384 /// // Two independently owned references to one kernel object, so closing
385 /// // one leaves the other usable.
386 /// assert_ne!(
387 /// first.as_handle().as_raw_handle(),
388 /// second.as_handle().as_raw_handle()
389 /// );
390 /// drop(second);
391 /// let adopted = fs::File::from(first.into_owned_handle());
392 /// assert_eq!(adopted.metadata()?.len(), 1);
393 /// # drop(file);
394 /// # drop(adopted);
395 /// # fs::remove_file(&path)?;
396 /// # Ok::<(), Box<dyn std::error::Error>>(())
397 /// ```
398 pub fn try_clone(&self) -> Result<Self, HandleCaptureError> {
399 Self::capture(self.duplicate.as_handle())
400 }
401
402 /// Releases the duplicate to the caller.
403 ///
404 /// The handle stays open; ownership moves.
405 #[must_use]
406 pub fn into_owned_handle(self) -> OwnedHandle {
407 self.duplicate
408 }
409
410 /// The duplicate's raw value, for passing to a Win32 call.
411 ///
412 /// The handle is borrowed, not transferred: it stays owned by this value
413 /// and must not outlive it or be closed by the caller.
414 pub(crate) fn raw(&self) -> HANDLE {
415 self.duplicate.as_raw_handle().cast::<c_void>()
416 }
417}
418
419impl AsHandle for CapturedHandle {
420 fn as_handle(&self) -> BorrowedHandle<'_> {
421 self.duplicate.as_handle()
422 }
423}
424
425impl From<CapturedHandle> for OwnedHandle {
426 fn from(captured: CapturedHandle) -> Self {
427 captured.into_owned_handle()
428 }
429}
430
431// Visible to the crate's own cross-module tests, which reuse this module's
432// fixture rather than standing up a second copy of it.
433#[cfg(test)]
434pub(crate) mod tests;