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;