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;