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;