Skip to main content

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;