windows-namespace-request-sys 0.2.1

Owned, marshalable parameter sets for synchronous Win32 namespace calls.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
// Copyright (c) Mike Grier.

//! Owned handle references.
//!
//! Five of the round-one entries take a handle rather than a path, so handle
//! ownership is a shared primitive rather than a detail of any one of them.
//! [`CapturedHandle`] is that primitive: it duplicates a caller's handle at
//! capture, owns the duplicate for its life, and closes it on drop.

use std::ffi::c_void;
use std::fmt;
use std::io;
use std::os::windows::io::{
    AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle, RawHandle,
};
use std::ptr;

use windows_sys::Win32::Foundation::{
    DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_INVALID_HANDLE, FALSE, HANDLE,
};
use windows_sys::Win32::System::Threading::GetCurrentProcess;

/// The handle values Windows reserves for pseudo-handles, as named constants
/// rather than bare integers.
///
/// A pseudo-handle is not a reference to a kernel object; it is a constant that
/// the calling thread resolves against *itself* at each use. Changing any value
/// here is a breaking change.
mod pseudo {
    /// `GetCurrentProcess`, and also `INVALID_HANDLE_VALUE`.
    pub const CURRENT_PROCESS: isize = -1;
    /// `GetCurrentThread`.
    pub const CURRENT_THREAD: isize = -2;
    /// Reserved by Windows; no documented producer.
    pub const RESERVED: isize = -3;
    /// `GetCurrentProcessToken`.
    pub const CURRENT_PROCESS_TOKEN: isize = -4;
    /// `GetCurrentThreadToken`.
    pub const CURRENT_THREAD_TOKEN: isize = -5;
    /// `GetCurrentThreadEffectiveToken`.
    pub const CURRENT_THREAD_EFFECTIVE_TOKEN: isize = -6;
}

/// Why a handle could not be captured.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum HandleCaptureFailure {
    /// The source handle was null.
    NullHandle,
    /// The source handle was `INVALID_HANDLE_VALUE`.
    ///
    /// Rejected explicitly rather than passed through, because
    /// `INVALID_HANDLE_VALUE` is *also* the current-process pseudo-handle:
    /// `DuplicateHandle` would accept it and hand back a perfectly valid handle
    /// to the current process, so an unchecked `CreateFileW` failure would be
    /// captured as a successful open of something else entirely.
    InvalidHandleValue,
    /// The source handle was one of the other Win32 pseudo-handles.
    ///
    /// A pseudo-handle names whatever the *using* thread is, so duplicating one
    /// on the caller's thread and using the result on a worker would silently
    /// change what it refers to.
    PseudoHandle,
    /// Windows refused to duplicate the source handle.
    ///
    /// A handle that has already been closed fails here, with
    /// `ERROR_INVALID_HANDLE`.
    DuplicateHandle,
}

/// A synchronous failure while capturing a caller's handle.
///
/// Duplication failure is a **construction** error by design: it is raised on
/// the calling thread, at the point the request is built, where the caller still
/// holds the source handle and can still do something about it. Deferring it to
/// execution would report a caller's mistake on a worker, to code that has no
/// way to correct it.
#[derive(Debug)]
pub struct HandleCaptureError {
    failure: HandleCaptureFailure,
    source: io::Error,
}

impl HandleCaptureError {
    fn new(failure: HandleCaptureFailure, source: io::Error) -> Self {
        Self { failure, source }
    }

    fn invalid_handle(failure: HandleCaptureFailure) -> Self {
        Self::new(
            failure,
            io::Error::from_raw_os_error(
                i32::try_from(ERROR_INVALID_HANDLE).expect("ERROR_INVALID_HANDLE fits in i32"),
            ),
        )
    }

    /// Why the capture failed.
    #[must_use]
    pub fn failure(&self) -> HandleCaptureFailure {
        self.failure
    }

    /// The underlying Win32 error code.
    #[must_use]
    pub fn raw_os_error(&self) -> Option<i32> {
        self.source.raw_os_error()
    }
}

impl fmt::Display for HandleCaptureError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let stage = match self.failure {
            HandleCaptureFailure::NullHandle => "null source handle",
            HandleCaptureFailure::InvalidHandleValue => "INVALID_HANDLE_VALUE source handle",
            HandleCaptureFailure::PseudoHandle => "pseudo-handle source handle",
            HandleCaptureFailure::DuplicateHandle => "DuplicateHandle",
        };

        write!(f, "{stage}: {}", self.source)
    }
}

impl std::error::Error for HandleCaptureError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.source)
    }
}

/// An owned duplicate of a handle a request names.
///
/// # A path is copied; a handle is duplicated
///
/// This is the distinction a caller reasoning in value semantics will get
/// wrong, so it is stated rather than implied: **a path is a value and is
/// copied; a handle is a reference to a kernel object, and duplicating it
/// shares that object rather than cloning it.**
///
/// A request holding a `CapturedHandle` is therefore self-contained with
/// respect to **lifetime** -- it cannot be left pointing at a handle its
/// originator closed, because it holds its own reference and closes it on drop
/// -- and is **not** isolated with respect to **state**. Measured, not reasoned:
///
/// - A duplicate **shares directory-enumeration state**: it continues where the
///   source stopped rather than starting its own listing. An independent
///   traversal needs a fresh open, not a duplicate.
/// - Closing the duplicate **does not disturb the source**. This is what makes
///   the whole design safe: a request may own a duplicate and drop it without
///   damaging the handle its caller kept.
/// - Single-shot metadata queries disturb nothing, on the source or on a
///   duplicate.
///
/// # What is duplicated
///
/// The duplicate carries the source's access rights (`DUPLICATE_SAME_ACCESS`),
/// because a request must be able to perform exactly the call the caller opened
/// the handle for. It is **not inheritable**, so capturing a handle never
/// widens what a child process can reach.
///
/// # Example
///
/// The lifetime guarantee, which is the reason this type exists: the capture
/// keeps working after its source is gone.
///
/// ```
/// use std::fs;
/// use std::os::windows::io::AsHandle;
///
/// use windows_namespace_request_sys::CapturedHandle;
///
/// let path = std::env::temp_dir().join(format!("wnrs-dup-{}.tmp", std::process::id()));
/// fs::write(&path, b"seven..")?;
///
/// let captured = {
///     let file = fs::File::open(&path)?;
///     CapturedHandle::capture(file.as_handle())?
///     // `file` is closed here; the duplicate is not.
/// };
///
/// let adopted = fs::File::from(captured.into_owned_handle());
/// assert_eq!(adopted.metadata()?.len(), 7);
/// # drop(adopted);
/// # fs::remove_file(&path)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Example: what a duplicate shares
///
/// The distinction a caller reasoning in value semantics gets backwards.
/// Closing the duplicate leaves the source perfectly usable, because both are
/// references to one kernel object rather than two copies of it:
///
/// ```
/// use std::fs;
/// use std::os::windows::io::AsHandle;
///
/// use windows_namespace_request_sys::CapturedHandle;
///
/// let path = std::env::temp_dir().join(format!("wnrs-dup2-{}.tmp", std::process::id()));
/// fs::write(&path, b"seven..")?;
/// let file = fs::File::open(&path)?;
///
/// let captured = CapturedHandle::capture(file.as_handle())?;
/// drop(captured);
///
/// // The source is untouched -- which is what makes it safe for a request to
/// // own a duplicate and drop it.
/// assert_eq!(file.metadata()?.len(), 7);
/// # drop(file);
/// # fs::remove_file(&path)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Debug)]
#[must_use = "dropping the captured handle closes the duplicate"]
pub struct CapturedHandle {
    duplicate: OwnedHandle,
}

impl CapturedHandle {
    /// Captures `source` by duplicating it into this process.
    ///
    /// # Errors
    ///
    /// Returns a [`HandleCaptureError`] when `source` is null, is
    /// `INVALID_HANDLE_VALUE`, is a Win32 pseudo-handle, or cannot be
    /// duplicated -- which is what an already-closed handle produces.
    ///
    /// # Example
    ///
    /// ```
    /// use std::fs;
    /// use std::os::windows::io::{AsHandle, AsRawHandle};
    ///
    /// use windows_namespace_request_sys::CapturedHandle;
    ///
    /// let path = std::env::temp_dir().join(format!("wnrs-cap-{}.tmp", std::process::id()));
    /// fs::write(&path, b"x")?;
    /// let file = fs::File::open(&path)?;
    ///
    /// let captured = CapturedHandle::capture(file.as_handle())?;
    ///
    /// // A duplicate is a second reference, so it has its own handle value.
    /// assert_ne!(captured.as_handle().as_raw_handle(), file.as_raw_handle());
    /// # drop(file);
    /// # drop(captured);
    /// # fs::remove_file(&path)?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// # Example: the failure that would otherwise be silent
    ///
    /// `INVALID_HANDLE_VALUE` and the current-process pseudo-handle are the
    /// same value, so an unchecked `CreateFileW` failure passed to
    /// `DuplicateHandle` would *succeed* and yield a process handle. Capture
    /// refuses it by name:
    ///
    /// ```
    /// use windows_namespace_request_sys::handle::HandleCaptureFailure;
    /// use windows_namespace_request_sys::CapturedHandle;
    /// use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
    ///
    /// // SAFETY: the value is validated, never dereferenced.
    /// let error = unsafe { CapturedHandle::capture_raw(INVALID_HANDLE_VALUE) }
    ///     .expect_err("INVALID_HANDLE_VALUE is never a real handle");
    ///
    /// assert_eq!(error.failure(), HandleCaptureFailure::InvalidHandleValue);
    /// ```
    pub fn capture(source: BorrowedHandle<'_>) -> Result<Self, HandleCaptureError> {
        // SAFETY: BorrowedHandle's invariant is that the handle it names stays
        // open for its borrow, which covers this call.
        unsafe { Self::capture_raw(source.as_raw_handle()) }
    }

    /// Captures a raw handle by duplicating it into this process.
    ///
    /// Prefer [`capture`](Self::capture) where an owned or borrowed handle is
    /// available. This form exists for the common case of a raw `HANDLE` that
    /// came straight back from a Win32 call and has no Rust owner yet.
    ///
    /// # Errors
    ///
    /// As [`capture`](Self::capture).
    ///
    /// # Safety
    ///
    /// `source` must remain open for the duration of this call. A handle closed
    /// concurrently may have had its value reused by another thread, in which
    /// case this captures a different kernel object rather than failing.
    pub unsafe fn capture_raw(source: RawHandle) -> Result<Self, HandleCaptureError> {
        if source.is_null() {
            return Err(HandleCaptureError::invalid_handle(
                HandleCaptureFailure::NullHandle,
            ));
        }

        match source as isize {
            pseudo::CURRENT_PROCESS => {
                return Err(HandleCaptureError::invalid_handle(
                    HandleCaptureFailure::InvalidHandleValue,
                ));
            }
            pseudo::CURRENT_THREAD
            | pseudo::RESERVED
            | pseudo::CURRENT_PROCESS_TOKEN
            | pseudo::CURRENT_THREAD_TOKEN
            | pseudo::CURRENT_THREAD_EFFECTIVE_TOKEN => {
                return Err(HandleCaptureError::invalid_handle(
                    HandleCaptureFailure::PseudoHandle,
                ));
            }
            _ => {}
        }

        let mut duplicate: HANDLE = ptr::null_mut();

        // SAFETY: GetCurrentProcess returns the current-process pseudo-handle,
        // which is exactly what DuplicateHandle wants for a same-process
        // duplication; source is a live handle per this function's contract;
        // duplicate points to writable storage. FALSE makes the duplicate
        // non-inheritable, and DUPLICATE_SAME_ACCESS makes the desired-access
        // argument ignored.
        let duplicated = unsafe {
            let process = GetCurrentProcess();
            DuplicateHandle(
                process,
                source,
                process,
                &raw mut duplicate,
                0,
                FALSE,
                DUPLICATE_SAME_ACCESS,
            )
        };
        if duplicated == FALSE {
            return Err(HandleCaptureError::new(
                HandleCaptureFailure::DuplicateHandle,
                io::Error::last_os_error(),
            ));
        }

        // SAFETY: a successful DuplicateHandle yields a new handle that this
        // process must release with CloseHandle, which OwnedHandle does.
        let duplicate = unsafe { OwnedHandle::from_raw_handle(duplicate) };
        Ok(Self { duplicate })
    }

    /// Captures a second, independently owned duplicate.
    ///
    /// This is not `Clone` because duplication is fallible. The result refers to
    /// the *same* kernel object, with everything that implies above.
    ///
    /// # Errors
    ///
    /// As [`capture`](Self::capture), though only
    /// [`HandleCaptureFailure::DuplicateHandle`] is reachable: the value being
    /// duplicated is already known to be a real, open handle.
    ///
    /// # Example
    ///
    /// ```
    /// use std::fs;
    /// use std::os::windows::io::{AsHandle, AsRawHandle};
    ///
    /// use windows_namespace_request_sys::CapturedHandle;
    ///
    /// let path = std::env::temp_dir().join(format!("wnrs-clone-{}.tmp", std::process::id()));
    /// fs::write(&path, b"x")?;
    /// let file = fs::File::open(&path)?;
    /// let first = CapturedHandle::capture(file.as_handle())?;
    ///
    /// let second = first.try_clone()?;
    ///
    /// // Two independently owned references to one kernel object, so closing
    /// // one leaves the other usable.
    /// assert_ne!(
    ///     first.as_handle().as_raw_handle(),
    ///     second.as_handle().as_raw_handle()
    /// );
    /// drop(second);
    /// let adopted = fs::File::from(first.into_owned_handle());
    /// assert_eq!(adopted.metadata()?.len(), 1);
    /// # drop(file);
    /// # drop(adopted);
    /// # fs::remove_file(&path)?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn try_clone(&self) -> Result<Self, HandleCaptureError> {
        Self::capture(self.duplicate.as_handle())
    }

    /// Releases the duplicate to the caller.
    ///
    /// The handle stays open; ownership moves.
    #[must_use]
    pub fn into_owned_handle(self) -> OwnedHandle {
        self.duplicate
    }

    /// The duplicate's raw value, for passing to a Win32 call.
    ///
    /// The handle is borrowed, not transferred: it stays owned by this value
    /// and must not outlive it or be closed by the caller.
    pub(crate) fn raw(&self) -> HANDLE {
        self.duplicate.as_raw_handle().cast::<c_void>()
    }
}

impl AsHandle for CapturedHandle {
    fn as_handle(&self) -> BorrowedHandle<'_> {
        self.duplicate.as_handle()
    }
}

impl From<CapturedHandle> for OwnedHandle {
    fn from(captured: CapturedHandle) -> Self {
        captured.into_owned_handle()
    }
}

// Visible to the crate's own cross-module tests, which reuse this module's
// fixture rather than standing up a second copy of it.
#[cfg(test)]
pub(crate) mod tests;