Skip to main content

windows_namespace_request_sys/
open_by_id.rs

1// Copyright (c) Mike Grier.
2
3//! The `OpenFileById` entry.
4//!
5//! Entry 2 of the audited catalogue, and a **second open primitive** rather
6//! than a variant of [`crate::open::OpenFile`]. It takes a volume-hint handle
7//! and a file identifier instead of a path, and it has no creation disposition
8//! at all -- it can only open something that already exists. One entry per
9//! Win32 call means it is its own entry.
10//!
11//! # Why a consumer reaches for this
12//!
13//! An identifier names a filesystem *object*, where a path names a location.
14//! Reopening by id is structurally incapable of landing on a different object
15//! than the one the id already named, while a fresh open against the original
16//! path cannot tell a recreated directory from the one a consumer started on.
17//! That difference is the whole reason the entry exists.
18//!
19//! # The volume hint is an input handle, and is held
20//!
21//! This is the first entry to take a handle as an *input*. The hint only needs to
22//! name some still-open handle on the same volume as the identifier -- it is never
23//! itself the object being reopened, so it stays valid even once that object is
24//! gone. The request holds it as one more holder of a `SharedHandle`, so the
25//! request cannot be left naming a hint its originator has closed.
26
27use std::ffi::c_void;
28use std::os::windows::io::{FromRawHandle, OwnedHandle};
29use std::ptr;
30
31use windows_sys::Win32::Storage::FileSystem::{
32    ExtendedFileIdType, FILE_FLAGS_AND_ATTRIBUTES, FILE_ID_128, FILE_ID_DESCRIPTOR,
33    FILE_ID_DESCRIPTOR_0, FILE_ID_TYPE, FILE_SHARE_MODE, FileIdType, ObjectIdType, OpenFileById,
34};
35use windows_sys::core::GUID;
36
37use win_shared_os_owned_handle::SharedHandle;
38
39use crate::handle::SharedRaw;
40use crate::outcome::{Outcome, perform_handle};
41use crate::security::{SecurityAttributes, SecurityCaptureError};
42
43/// Which kind of identifier names the object to open.
44///
45/// Win32 spells this as a tagged union whose tag and payload must be kept in
46/// step by hand; here the tag is implied by the variant, so the two cannot
47/// disagree.
48///
49/// All three forms are supported. Only [`FileId`](Self::FileId) appears in the
50/// audited consumers, but an entry that could express one of its own call's
51/// three identifier kinds would be a narrowed `OpenFileById`.
52///
53/// # Example
54///
55/// The tag is implied by the variant, so it cannot disagree with the payload
56/// the way the raw union permits:
57///
58/// ```
59/// use windows_namespace_request_sys::open_by_id::FileIdentifier;
60/// use windows_sys::Win32::Storage::FileSystem::{
61///     ExtendedFileIdType, FileIdType, ObjectIdType,
62/// };
63///
64/// assert_eq!(FileIdentifier::FileId(7).id_type(), FileIdType);
65/// assert_eq!(FileIdentifier::ObjectId(0x1234).id_type(), ObjectIdType);
66/// assert_eq!(FileIdentifier::ExtendedFileId([0; 16]).id_type(), ExtendedFileIdType);
67/// ```
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub enum FileIdentifier {
70    /// A 64-bit file reference number, as reported by `FileIdInfo`'s
71    /// predecessor and by `BY_HANDLE_FILE_INFORMATION`. This is the form every
72    /// audited consumer uses.
73    FileId(u64),
74    /// A volume-scoped object identifier.
75    ///
76    /// Taken as a `u128` rather than a `GUID` deliberately. Win32's `GUID` is a
77    /// dependency's type that implements neither equality nor `Debug`, and a
78    /// public surface should not be shaped by whichever binding crate this
79    /// happens to build against. The conversion to `GUID` happens at the FFI
80    /// boundary, where it belongs.
81    ///
82    /// The value is the `GUID` read as one big-endian number in the order of
83    /// its canonical text form: `7383249c-cb6e-11f1-857d-7ced8d529076` is
84    /// `0x7383_249c_cb6e_11f1_857d_7ced_8d52_9076`. That is `GUID::from_u128`'s
85    /// reading, and is not the order of the 16 bytes Windows stores, whose first
86    /// three fields are little-endian.
87    ObjectId(u128),
88    /// A 128-bit file reference number, as reported by `FileIdInfo` on ReFS,
89    /// where 64 bits is not enough to name a file.
90    /// [`FileIdInfo::file_id`](crate::file_id::FileIdInfo::file_id) is that value.
91    ExtendedFileId([u8; 16]),
92}
93
94impl FileIdentifier {
95    /// The `FILE_ID_TYPE` tag Win32 pairs with this identifier.
96    #[must_use]
97    pub fn id_type(self) -> FILE_ID_TYPE {
98        match self {
99            Self::FileId(_) => FileIdType,
100            Self::ObjectId(_) => ObjectIdType,
101            Self::ExtendedFileId(_) => ExtendedFileIdType,
102        }
103    }
104
105    /// Builds the Win32 descriptor, with its tag and payload necessarily in
106    /// step.
107    fn to_descriptor(self) -> FILE_ID_DESCRIPTOR {
108        let anonymous = match self {
109            Self::FileId(id) => FILE_ID_DESCRIPTOR_0 {
110                FileId: id.cast_signed(),
111            },
112            Self::ObjectId(id) => FILE_ID_DESCRIPTOR_0 {
113                ObjectId: GUID::from_u128(id),
114            },
115            Self::ExtendedFileId(id) => FILE_ID_DESCRIPTOR_0 {
116                ExtendedFileId: FILE_ID_128 { Identifier: id },
117            },
118        };
119
120        FILE_ID_DESCRIPTOR {
121            dwSize: u32::try_from(size_of::<FILE_ID_DESCRIPTOR>())
122                .expect("this fixed, small struct's size always fits a u32"),
123            Type: self.id_type(),
124            Anonymous: anonymous,
125        }
126    }
127}
128
129/// An owned, marshalable parameter set for `OpenFileById`.
130///
131/// # Example
132///
133/// ```
134/// use std::fs;
135///
136/// use windows_namespace_request_sys::open_by_id::{FileIdentifier, OpenFileByIdentifier};
137/// use windows_sys::Win32::Storage::FileSystem::{
138///     FILE_GENERIC_READ, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
139/// };
140///
141/// let path = std::env::temp_dir().join(format!("wnrs-byid-{}.tmp", std::process::id()));
142/// fs::write(&path, b"example")?;
143/// let file = fs::File::open(&path)?;
144///
145/// // Any open handle on the same volume will do as the hint; it is never the
146/// // object being reopened. The request holds it, shared.
147/// let request = OpenFileByIdentifier::new(file, FileIdentifier::FileId(0))
148///     .with_desired_access(FILE_GENERIC_READ)
149///     .with_share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE);
150///
151/// assert_eq!(request.identifier(), FileIdentifier::FileId(0));
152/// # drop(request);
153/// # fs::remove_file(&path)?;
154/// # Ok::<(), Box<dyn std::error::Error>>(())
155/// ```
156#[derive(Debug)]
157#[must_use = "an unperformed request opens nothing"]
158pub struct OpenFileByIdentifier {
159    volume_hint: SharedHandle,
160    identifier: FileIdentifier,
161    desired_access: u32,
162    share_mode: FILE_SHARE_MODE,
163    security: Option<SecurityAttributes>,
164    flags_and_attributes: FILE_FLAGS_AND_ATTRIBUTES,
165}
166
167impl OpenFileByIdentifier {
168    /// Begins a request to reopen `identifier`, using `volume_hint` to name the
169    /// volume it lives on.
170    ///
171    /// As with [`crate::open::OpenFile`], the remaining parameters start at
172    /// "the caller said nothing" and are set explicitly. There is no creation
173    /// disposition to set: `OpenFileById` has none, which is one of the reasons
174    /// this is a separate entry rather than a variant. The request holds `volume_hint` as one
175    /// more holder of it.
176    pub fn new(volume_hint: impl Into<SharedHandle>, identifier: FileIdentifier) -> Self {
177        Self {
178            volume_hint: volume_hint.into(),
179            identifier,
180            desired_access: 0,
181            share_mode: 0,
182            security: None,
183            flags_and_attributes: 0,
184        }
185    }
186
187    /// Sets `dwDesiredAccess`.
188    pub fn with_desired_access(mut self, desired_access: u32) -> Self {
189        self.desired_access = desired_access;
190        self
191    }
192
193    /// Sets `dwShareMode`.
194    pub fn with_share_mode(mut self, share_mode: FILE_SHARE_MODE) -> Self {
195        self.share_mode = share_mode;
196        self
197    }
198
199    /// Sets `lpSecurityAttributes` from an already-captured value.
200    pub fn with_security(mut self, security: Option<SecurityAttributes>) -> Self {
201        self.security = security;
202        self
203    }
204
205    /// Sets `dwFlagsAndAttributes`, carried verbatim as in
206    /// [`crate::open::OpenFile`].
207    pub fn with_flags_and_attributes(
208        mut self,
209        flags_and_attributes: FILE_FLAGS_AND_ATTRIBUTES,
210    ) -> Self {
211        self.flags_and_attributes = flags_and_attributes;
212        self
213    }
214
215    /// The volume-hint handle, shared with its other holders.
216    pub fn volume_hint(&self) -> &SharedHandle {
217        &self.volume_hint
218    }
219
220    /// The identifier this request will reopen.
221    #[must_use]
222    pub fn identifier(&self) -> FileIdentifier {
223        self.identifier
224    }
225
226    /// The requested access mask.
227    #[must_use]
228    pub fn desired_access(&self) -> u32 {
229        self.desired_access
230    }
231
232    /// The requested share mode.
233    #[must_use]
234    pub fn share_mode(&self) -> FILE_SHARE_MODE {
235        self.share_mode
236    }
237
238    /// The captured security attributes, if any were supplied.
239    #[must_use]
240    pub fn security(&self) -> Option<&SecurityAttributes> {
241        self.security.as_ref()
242    }
243
244    /// The requested flags and attributes.
245    #[must_use]
246    pub fn flags_and_attributes(&self) -> FILE_FLAGS_AND_ATTRIBUTES {
247        self.flags_and_attributes
248    }
249
250    /// Copies the request, sharing the volume-hint handle.
251    ///
252    /// Not `Clone`, for the same reason [`crate::open::OpenFile::try_clone`] is not: copying the
253    /// security attributes it may own is fallible. The hint is shared, not duplicated (`D-32` in
254    /// the crate's `DESIGN-NOTES.md`), so it cannot fail.
255    ///
256    /// # Errors
257    ///
258    /// Returns [`SecurityAttributes::try_clone`]'s error when the memory for the security
259    /// attributes cannot be allocated. A request without them cannot fail.
260    pub fn try_clone(&self) -> Result<Self, SecurityCaptureError> {
261        let volume_hint = self.volume_hint.clone();
262        let security = self
263            .security
264            .as_ref()
265            .map(SecurityAttributes::try_clone)
266            .transpose()?;
267
268        Ok(Self {
269            volume_hint,
270            identifier: self.identifier,
271            desired_access: self.desired_access,
272            share_mode: self.share_mode,
273            security,
274            flags_and_attributes: self.flags_and_attributes,
275        })
276    }
277
278    /// Performs the open on the calling thread.
279    ///
280    /// The handle comes back plain and unassociated, as with every other
281    /// handle-producing entry.
282    ///
283    /// # Errors
284    ///
285    /// Returns the raw Win32 code, unaltered. `ERROR_INVALID_PARAMETER` here
286    /// most often means the identified object no longer exists, but nothing in
287    /// this crate infers that on the caller's behalf.
288    pub fn perform(&self) -> Outcome<OwnedHandle> {
289        let descriptor = self.identifier.to_descriptor();
290        let attributes = self.security.as_ref().map(SecurityAttributes::to_raw);
291        let attributes_ptr = attributes.as_ref().map_or(ptr::null(), ptr::from_ref);
292
293        let raw = perform_handle(|| {
294            // SAFETY: the volume hint is one this request holds and
295            // keeps open across the call; the descriptor is fully initialised
296            // with its tag and payload in step and is only read; the security
297            // attributes, when present, point at a self-relative descriptor
298            // this request owns.
299            unsafe {
300                OpenFileById(
301                    self.volume_hint.raw(),
302                    &raw const descriptor,
303                    self.desired_access,
304                    self.share_mode,
305                    attributes_ptr,
306                    self.flags_and_attributes,
307                )
308            }
309        })?;
310
311        // SAFETY: a successful OpenFileById returns a handle this process owns
312        // exclusively and must release with CloseHandle, which OwnedHandle
313        // does.
314        Ok(unsafe { OwnedHandle::from_raw_handle(raw.cast::<c_void>()) })
315    }
316}
317
318impl crate::request::Request for OpenFileByIdentifier {
319    type Error = crate::Win32Error;
320    type Output = OwnedHandle;
321
322    fn perform(&self) -> Outcome<OwnedHandle> {
323        Self::perform(self)
324    }
325}
326
327#[cfg(test)]
328mod tests;