Skip to main content

win_durable_ioring/windows_backend/opened_file/
by_id.rs

1// Copyright (c) 2026 Mike Grier
2//! An open by identifier, through `OpenFileById` (DI-15.2, DI-D-70): the file is named by a
3//! [`FileIdentifier`] on the volume of a hint, and not by a path.
4//!
5//! The options are a type of their own, [`ByIdOptions`], and not [`FileOptions`](super::FileOptions):
6//! the call's page lists no creation disposition, takes no attributes, marks the security parameter
7//! reserved, and lists eight flags, of which `FILE_FLAG_DELETE_ON_CLOSE` and
8//! `FILE_FLAG_POSIX_SEMANTICS` are not two. What a handle this crate opened can claim depends on how
9//! it was opened and not on how it was named, so the file is the same [`OpenedFile`], and the checks
10//! made on the handle are the path open's (DI-14.5).
11//!
12//! The hint is any handle on the volume, whatever opened it: it is never the file opened, so its mode
13//! reaches nothing the result claims (`DI-D-64`'s `hVolumeHint` row).
14
15use std::fmt;
16use std::io;
17use std::marker::PhantomData;
18
19use win_shared_os_owned_handle::SharedHandle;
20use windows_namespace_request_sys::open_by_id::OpenFileByIdentifier;
21use windows_sys::Win32::Storage::FileSystem::{
22    FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED,
23    FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN,
24};
25
26use durable_ioring::{DataAccess, NoData, ReadWrite};
27
28use super::{Buffering, Cached, OpenedBy, OpenedFile, data_access, data_rights, refine_sharing_by};
29use crate::windows_backend::file_id::{FileId, FileIdentifier};
30use crate::windows_backend::file_rights::FileRights;
31use crate::windows_backend::share_mode::ShareMode;
32
33#[cfg(test)]
34mod tests;
35
36/// What to open by identifier: the data access, the other rights, what other opens may do meanwhile,
37/// whether a link is opened or followed, and the hints, which are what `OpenFileById` takes. A file is
38/// shared for reading, writing and deletion until [`share`](Self::share) says otherwise, as
39/// [`FileOptions`](super::FileOptions) shares it.
40///
41/// There is no disposition, since the call opens only what exists; no attributes, which its page
42/// says it ignores; no security descriptor, which it marks reserved; and no delete-on-close or POSIX
43/// semantics, which it does not list. `FILE_FLAG_BACKUP_SEMANTICS`, which a directory needs, and
44/// `FILE_FLAG_WRITE_THROUGH` are not in v1, as for an open by path.
45///
46/// ```
47/// use win_durable_ioring::windows_backend::{ByIdOptions, ReadOnly, ShareMode};
48///
49/// let options: ByIdOptions<ReadOnly> = ByIdOptions::new().read().share(ShareMode::READ);
50/// ```
51#[derive(Clone, Copy, Debug)]
52#[must_use]
53pub struct ByIdOptions<A: DataAccess = NoData> {
54    access: PhantomData<A>,
55    rights: FileRights,
56    share: ShareMode,
57    open_reparse_point: bool,
58    open_no_recall: bool,
59    overlapped: bool,
60    random_access: bool,
61    sequential_scan: bool,
62}
63
64impl Default for ByIdOptions<NoData> {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl ByIdOptions<NoData> {
71    /// Nothing requested: no data access and no other right, shared for everything.
72    pub fn new() -> Self {
73        Self {
74            access: PhantomData,
75            rights: FileRights::NONE,
76            share: ShareMode::ALL,
77            open_reparse_point: false,
78            open_no_recall: false,
79            overlapped: false,
80            random_access: false,
81            sequential_scan: false,
82        }
83    }
84}
85
86impl<A: DataAccess> ByIdOptions<A> {
87    /// Ask for the right to read the data, as [`FileOptions::read`](super::FileOptions::read) does.
88    pub fn read(self) -> ByIdOptions<A::WithRead> {
89        self.with_access()
90    }
91
92    /// Ask for the right to write the data, as [`FileOptions::write`](super::FileOptions::write) does.
93    pub fn write(self) -> ByIdOptions<A::WithWrite> {
94        self.with_access()
95    }
96
97    /// The rights besides the data access, as [`FileOptions::rights`](super::FileOptions::rights)
98    /// takes them. Calling this again replaces them.
99    pub fn rights(mut self, rights: FileRights) -> Self {
100        self.rights = rights;
101        self
102    }
103
104    /// What other opens may request while this handle is open (`dwShareMode`), as
105    /// [`FileOptions::share`](super::FileOptions::share) takes it. Calling this again replaces it.
106    pub fn share(mut self, share: ShareMode) -> Self {
107        self.share = share;
108        self
109    }
110
111    /// Open a reparse point itself and not what it points to (`FILE_FLAG_OPEN_REPARSE_POINT`). The
112    /// page says the flag is ignored for a file that is not a reparse point.
113    pub fn open_reparse_point(mut self, open_reparse_point: bool) -> Self {
114        self.open_reparse_point = open_reparse_point;
115        self
116    }
117
118    /// Ask that the data not be brought back from remote storage (`FILE_FLAG_OPEN_NO_RECALL`), as
119    /// [`FileOptions::open_no_recall`](super::FileOptions::open_no_recall) does.
120    pub fn open_no_recall(mut self, open_no_recall: bool) -> Self {
121        self.open_no_recall = open_no_recall;
122        self
123    }
124
125    /// Open for overlapped I/O (`FILE_FLAG_OVERLAPPED`), as
126    /// [`FileOptions::overlapped`](super::FileOptions::overlapped) describes.
127    pub fn overlapped(mut self, overlapped: bool) -> Self {
128        self.overlapped = overlapped;
129        self
130    }
131
132    /// The random-access hint (`FILE_FLAG_RANDOM_ACCESS`), as
133    /// [`FileOptions::random_access`](super::FileOptions::random_access) describes.
134    pub fn random_access(mut self, random_access: bool) -> Self {
135        self.random_access = random_access;
136        self
137    }
138
139    /// The sequential-scan hint (`FILE_FLAG_SEQUENTIAL_SCAN`), as
140    /// [`FileOptions::sequential_scan`](super::FileOptions::sequential_scan) describes.
141    pub fn sequential_scan(mut self, sequential_scan: bool) -> Self {
142        self.sequential_scan = sequential_scan;
143        self
144    }
145
146    fn with_access<A2: DataAccess>(self) -> ByIdOptions<A2> {
147        ByIdOptions {
148            access: PhantomData,
149            rights: self.rights,
150            share: self.share,
151            open_reparse_point: self.open_reparse_point,
152            open_no_recall: self.open_no_recall,
153            overlapped: self.overlapped,
154            random_access: self.random_access,
155            sequential_scan: self.sequential_scan,
156        }
157    }
158
159    /// `dwDesiredAccess`: the data rights `A` names and the other rights.
160    fn access(self) -> u32 {
161        data_rights::<A>() | self.rights.bits()
162    }
163
164    /// `dwFlagsAndAttributes`: `mode`, the buffering mode's flags, and the flags the options name.
165    fn flags(self, mode: u32) -> u32 {
166        let mut flags = mode;
167        if self.open_reparse_point {
168            flags |= FILE_FLAG_OPEN_REPARSE_POINT;
169        }
170        if self.open_no_recall {
171            flags |= FILE_FLAG_OPEN_NO_RECALL;
172        }
173        if self.overlapped {
174            flags |= FILE_FLAG_OVERLAPPED;
175        }
176        if self.random_access {
177            flags |= FILE_FLAG_RANDOM_ACCESS;
178        }
179        if self.sequential_scan {
180            flags |= FILE_FLAG_SEQUENTIAL_SCAN;
181        }
182        flags
183    }
184}
185
186/// The request to open a file by identifier, built by [`OpenedFile::request_by_id`] and made by
187/// [`perform`](Self::perform). It holds the hint, shared, so the handle it was built from can be
188/// closed first; it is `Send`, so it can be performed on a thread that can afford to block; and it
189/// can be performed more than once, each time opening the file again.
190#[derive(Debug)]
191#[must_use = "an unperformed request opens nothing"]
192pub struct ByIdRequest<B: Buffering = Cached, A: DataAccess = ReadWrite> {
193    request: OpenFileByIdentifier,
194    identifier: FileIdentifier,
195    rights: FileRights,
196    share: ShareMode,
197    mode: PhantomData<fn() -> (B, A)>,
198}
199
200impl<B: Buffering, A: DataAccess> ByIdRequest<B, A> {
201    /// The identifier this request opens.
202    #[must_use]
203    pub fn identifier(&self) -> FileIdentifier {
204        self.identifier
205    }
206
207    /// The hint whose volume the identifier is opened on, shared with its other holders.
208    #[must_use]
209    pub fn volume_hint(&self) -> &SharedHandle {
210        self.request.volume_hint()
211    }
212
213    /// Open the file, on the calling thread. **This blocks for as long as `OpenFileById` does.**
214    ///
215    /// # Errors
216    ///
217    /// - [`SharingViolation`](super::SharingViolation) (as an [`io::ErrorKind::ResourceBusy`] error),
218    ///   naming the file by [`OpenedBy::Identifier`], when the file is open in a way that conflicts
219    ///   with this open's access or sharing.
220    /// - For a [`FileIdentifier::Extended`], [`IdentityMismatch`] (as an
221    ///   [`io::ErrorKind::InvalidInput`] error) when the file opened is not the identity asked for,
222    ///   which is a hint on another volume; the handle is closed. An error reading the opened file's
223    ///   identity is returned as it came.
224    /// - The path open's checks, [`RemoteDevice`](super::RemoteDevice) and
225    ///   [`UnknownLocation`](super::UnknownLocation), made on the opened handle by the same code. No
226    ///   test reaches them by identifier: the page lists SMB as not supported, so an open by
227    ///   identifier on a share is the kernel's to refuse, and the call opens only a file on a file
228    ///   system, whose final path is readable.
229    /// - Otherwise whatever `OpenFileById` reports, unaltered. The page names one, `ERROR_ACCESS_DENIED`
230    ///   for a file pending deletion; an identifier that names nothing is the kernel's error, and the
231    ///   page does not say which.
232    pub fn perform(&self) -> io::Result<OpenedFile<B, A>> {
233        let handle = self.request.perform().map_err(|error| {
234            refine_sharing_by(
235                error.to_io_error(),
236                || OpenedBy::Identifier(self.identifier),
237                data_access::<A>(),
238                self.rights,
239                self.share,
240            )
241        })?;
242        let handle = SharedHandle::from(handle);
243        if let FileIdentifier::Extended(asked) = self.identifier {
244            let opened = FileId::of(&handle)?;
245            if opened != asked {
246                return Err(io::Error::new(
247                    io::ErrorKind::InvalidInput,
248                    IdentityMismatch { asked, opened },
249                ));
250            }
251        }
252        OpenedFile::admit(handle)
253    }
254}
255
256impl<B: Buffering, A: DataAccess> OpenedFile<B, A> {
257    /// Open the file `identifier` names on the volume `volume_hint` is on, as `options` say, in
258    /// buffering mode `B`: [`request_by_id`](Self::request_by_id) and then
259    /// [`perform`](ByIdRequest::perform), one after the other on this thread.
260    ///
261    /// ```
262    /// use win_durable_ioring::windows_backend::{ByIdOptions, Cached, FileOptions, OpenedFile};
263    /// use win_durable_ioring::Wtf16String;
264    ///
265    /// let path = std::env::temp_dir().join(format!("by-id-doc-{}.tmp", std::process::id()));
266    /// std::fs::write(&path, b"x")?;
267    /// let name = Wtf16String::from_os_str(path.as_os_str());
268    /// let file = OpenedFile::<Cached, _>::open(name, FileOptions::new().read())?;
269    ///
270    /// // Any handle on the volume is a hint; here, the file's own.
271    /// let again = OpenedFile::<Cached, _>::open_by_id(
272    ///     file.handle().clone(),
273    ///     file.file_id()?,
274    ///     ByIdOptions::new().read(),
275    /// )?;
276    /// assert_eq!(again.file_id()?, file.file_id()?);
277    /// # drop((file, again));
278    /// # std::fs::remove_file(&path)?;
279    /// # Ok::<(), Box<dyn std::error::Error>>(())
280    /// ```
281    ///
282    /// **An identifier can name a directory, and it is opened.** `FILE_FLAG_BACKUP_SEMANTICS`, which
283    /// the page says a directory needs, is not in v1, and an open by identifier was observed to reach a
284    /// directory without it. It is not refused: a target made from it faults every read and write with
285    /// the kernel's error, and a write that faulted is covered by no durability report, so the commits
286    /// it completes vouch for nothing. On a directory the data rights are `FILE_LIST_DIRECTORY` and
287    /// `FILE_ADD_FILE`, the same bits, and that is what the lent handle carries. What a flush of a
288    /// directory makes durable no page says. The design notes' `DI-D-72` has the measurement.
289    ///
290    /// # Errors
291    ///
292    /// Those of [`perform`](ByIdRequest::perform).
293    pub fn open_by_id(
294        volume_hint: impl Into<SharedHandle>,
295        identifier: impl Into<FileIdentifier>,
296        options: ByIdOptions<A>,
297    ) -> io::Result<Self> {
298        Self::request_by_id(volume_hint, identifier, options).perform()
299    }
300
301    /// The request to open the file `identifier` names on the volume `volume_hint` is on, built here
302    /// and made wherever the caller chooses with [`perform`](ByIdRequest::perform). Nothing is opened
303    /// and nothing blocks. The hint is held, shared, and not duplicated.
304    pub fn request_by_id(
305        volume_hint: impl Into<SharedHandle>,
306        identifier: impl Into<FileIdentifier>,
307        options: ByIdOptions<A>,
308    ) -> ByIdRequest<B, A> {
309        let identifier = identifier.into();
310        let request = OpenFileByIdentifier::new(volume_hint, identifier.to_request())
311            .with_desired_access(options.access())
312            .with_share_mode(options.share.bits())
313            .with_flags_and_attributes(options.flags(B::FLAGS));
314        ByIdRequest {
315            request,
316            identifier,
317            rights: options.rights,
318            share: options.share,
319            mode: PhantomData,
320        }
321    }
322}
323
324/// An open by a [`FileIdentifier::Extended`] opened a file whose identity is not the one asked for:
325/// the call takes the volume from the hint and not from the [`FileId`], so a hint on another volume
326/// opens whatever has that identifier there, or nothing. The handle is closed.
327///
328/// It is the cause of an [`io::ErrorKind::InvalidInput`] error from [`OpenedFile::open_by_id`],
329/// reached with [`io::Error::get_ref`] and a downcast.
330#[derive(Clone, Copy, Debug, PartialEq, Eq)]
331pub struct IdentityMismatch {
332    /// The identity the open was asked for.
333    pub asked: FileId,
334    /// The identity of the file it opened.
335    pub opened: FileId,
336}
337
338impl fmt::Display for IdentityMismatch {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        write!(
341            f,
342            "the open by identifier asked for {:?} and opened {:?}: the volume hint is not on the \
343             volume the identity names",
344            self.asked, self.opened
345        )
346    }
347}
348
349impl std::error::Error for IdentityMismatch {}