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