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