Skip to main content

win_durable_ioring/windows_backend/
opened_file.rs

1// Copyright (c) 2026 Mike Grier
2//! [`OpenedFile`]: a file this crate opened, so how it was opened is known by construction
3//! (DI-D-60).
4//!
5//! A [`Win32FileTarget`](super::Win32FileTarget) states a warranty class, an alignment and what it
6//! offers, and every one of them depends on how its file was opened: no intermediate buffering
7//! is what lets a file claim [`WarrantyClass::SharedDevice`], and what makes its operations need a
8//! sector's alignment. A handle someone else opened cannot be asked, because the only call that
9//! reports the open mode is undocumented, which this repository does not call. So the file is
10//! opened here, by a type that records what it did, and a target can be made from nothing else.
11//!
12//! The buffering mode is a type: [`OpenedFile<Cached>`] or [`OpenedFile<Unbuffered>`]. What a mode
13//! implies is stated once, on [`Buffering`]; a consumer that needs the stronger warranty asks for
14//! `OpenedFile<Unbuffered>` in its own signature, and a cached file cannot be passed.
15//!
16//! The open reflects `CreateFileW` and not `std::fs::OpenOptions` (DI-D-64): the path is a
17//! `Wtf16String` handed over as given, the [`Disposition`] is Win32's own, and no option implies
18//! another (a new file does not set the reparse flag by itself). It is a build,
19//! [`OpenedFile::request`], and a perform, [`OpenRequest::perform`], with
20//! [`OpenedFile::open`] the two run together.
21
22use std::fmt;
23use std::io;
24use std::marker::PhantomData;
25use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle};
26use std::path::{Component, Path, PathBuf, Prefix};
27
28use win_authorization_sys::{SecurityAttributes, SecurityDescriptor};
29use win_shared_os_owned_handle::SharedHandle;
30use windows_namespace_request_sys::open::OpenFile;
31use windows_namespace_request_sys::{FinalPathError, QueryFinalPath};
32use windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION;
33use windows_sys::Win32::Storage::FileSystem::{
34    FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT,
35    FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS,
36    FILE_FLAG_SEQUENTIAL_SCAN, FILE_READ_DATA, FILE_WRITE_DATA,
37};
38use wtf_string::{Wtf16Str, Wtf16String};
39
40use durable_ioring::{
41    Access, Capabilities, DataAccess, Geometry, GeometryError, NoData, ReadWrite, WarrantyClass,
42};
43
44use super::disposition::Disposition;
45use super::file_attributes::FileAttributes;
46use super::file_id::FileIdentifier;
47use super::file_rights::FileRights;
48use super::share_mode::ShareMode;
49
50mod by_id;
51mod open_request;
52mod reopen;
53
54pub use by_id::{ByIdOptions, ByIdRequest, IdentityMismatch};
55pub use reopen::{ReopenOptions, ReopenRequest};
56
57#[cfg(test)]
58mod creation_tests;
59#[cfg(test)]
60mod flag_tests;
61#[cfg(test)]
62mod request_tests;
63#[cfg(test)]
64mod share_tests;
65#[cfg(test)]
66mod tests;
67
68/// `FILE_FLAG_NO_BUFFERING`, as `CreateFileW` documents it: the file is opened without the system
69/// cache, so every transfer needs the device's alignment.
70const FILE_FLAG_NO_BUFFERING: u32 = 0x2000_0000;
71
72mod sealed {
73    pub trait Sealed {}
74}
75
76/// How a file is buffered, which decides what a target made from it can claim. Sealed: there are
77/// two modes, [`Cached`] and [`Unbuffered`], and a warranty is a statement about each of them that
78/// this crate makes.
79pub trait Buffering: sealed::Sealed + Copy + fmt::Debug + Send + Sync + 'static {
80    /// The `CreateFileW` flags this mode opens with.
81    const FLAGS: u32;
82
83    /// The warranty class a file in this mode can claim.
84    const WARRANTY: WarrantyClass;
85
86    /// What a file in this mode offers beyond the contract's obligations.
87    const CAPABILITIES: Capabilities;
88
89    /// Whether the claim in [`WARRANTY`](Self::WARRANTY) holds only on a device of this machine, so
90    /// that a file on a remote one is refused rather than given a class it cannot keep.
91    const LOCAL_DEVICE_ONLY: bool;
92
93    /// The alignment a file in this mode starts with.
94    fn geometry() -> Geometry;
95
96    /// The alignment a raw volume or disk in this mode needs, addressed in sectors of `sector_size`
97    /// bytes. Its transfers are whole sectors in either mode, so offsets and lengths are aligned to
98    /// one; its buffers only when it is unbuffered, as the cache is what lets a cached transfer
99    /// start anywhere.
100    ///
101    /// # Errors
102    ///
103    /// [`GeometryError`] if `sector_size` is not a power of two.
104    fn sector_geometry(sector_size: u32) -> Result<Geometry, GeometryError>;
105}
106
107/// A file opened for cached I/O: the system cache holds its writes, and its write-back can fail
108/// without telling the writer, so it claims [`WarrantyClass::BestEffort`] and needs no alignment.
109#[derive(Clone, Copy, Debug)]
110pub struct Cached;
111
112/// A file opened with no intermediate buffering: its transfers reach the device, so it can claim
113/// [`WarrantyClass::SharedDevice`], and need the device's sector alignment. Per-write
114/// write-through is honoured only on such a file, which the kernel refuses on a cached one
115/// (error 509, measured 2026-10-07 and kept as a test), so it offers that capability.
116#[derive(Clone, Copy, Debug)]
117pub struct Unbuffered;
118
119impl sealed::Sealed for Cached {}
120impl sealed::Sealed for Unbuffered {}
121
122impl Buffering for Cached {
123    const FLAGS: u32 = 0;
124    const WARRANTY: WarrantyClass = WarrantyClass::BestEffort;
125    const CAPABILITIES: Capabilities = Capabilities::NONE;
126    const LOCAL_DEVICE_ONLY: bool = false;
127
128    fn geometry() -> Geometry {
129        Geometry::UNALIGNED
130    }
131
132    fn sector_geometry(sector_size: u32) -> Result<Geometry, GeometryError> {
133        Geometry::new(sector_size, sector_size, 1)
134    }
135}
136
137impl Unbuffered {
138    /// The sector size an unbuffered file is given until [`OpenedFile::with_sector_size`] says
139    /// otherwise: the largest in general use, which every smaller one divides, so no device
140    /// refuses it. A caller whose device allows less can say so and be offered less alignment to
141    /// meet.
142    pub const DEFAULT_SECTOR_SIZE: u32 = 4096;
143}
144
145impl Buffering for Unbuffered {
146    const FLAGS: u32 = FILE_FLAG_NO_BUFFERING;
147    const WARRANTY: WarrantyClass = WarrantyClass::SharedDevice;
148    const CAPABILITIES: Capabilities = Capabilities::NONE.with_write_through();
149    const LOCAL_DEVICE_ONLY: bool = true;
150
151    fn geometry() -> Geometry {
152        Geometry::uniform(Self::DEFAULT_SECTOR_SIZE).expect("4096 is a power of two")
153    }
154
155    fn sector_geometry(sector_size: u32) -> Result<Geometry, GeometryError> {
156        Geometry::uniform(sector_size)
157    }
158}
159
160/// What to open: the data access, the other rights, what other opens may do meanwhile, what the open
161/// does about a file that is there or not, whether a link is opened or followed, and the cache hints.
162/// Only these, because the open mode is what an [`OpenedFile`] vouches for, and a caller's own flags
163/// could change it. A file is shared for reading, writing and deletion, as std opens one, until
164/// [`share`](Self::share) says otherwise.
165///
166/// The data access is the type parameter `A`: it starts as [`NoData`], and [`read`](Self::read) and
167/// [`write`](Self::write) move it, so the handle an open makes carries the access it was asked
168/// for.
169///
170/// ```
171/// use win_durable_ioring::windows_backend::{FileOptions, FileRights, ReadWrite};
172///
173/// let options: FileOptions<ReadWrite> = FileOptions::new()
174///     .read()
175///     .write()
176///     .rights(FileRights::WRITE_ATTRIBUTES);
177/// ```
178#[derive(Clone, Copy, Debug)]
179#[must_use]
180pub struct FileOptions<A: DataAccess = NoData> {
181    access: PhantomData<A>,
182    rights: FileRights,
183    share: ShareMode,
184    disposition: Disposition,
185    open_reparse_point: bool,
186    attributes: FileAttributes,
187    delete_on_close: bool,
188    open_no_recall: bool,
189    posix_semantics: bool,
190    overlapped: bool,
191    random_access: bool,
192    sequential_scan: bool,
193}
194
195impl Default for FileOptions<NoData> {
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201impl FileOptions<NoData> {
202    /// Nothing requested: no data access and no other right. Ask to [`read`](Self::read) and
203    /// [`write`](Self::write) before opening a file that is to become a target.
204    pub fn new() -> Self {
205        Self {
206            access: PhantomData,
207            rights: FileRights::NONE,
208            share: ShareMode::ALL,
209            disposition: Disposition::OpenExisting,
210            open_reparse_point: false,
211            attributes: FileAttributes::NONE,
212            delete_on_close: false,
213            open_no_recall: false,
214            posix_semantics: false,
215            overlapped: false,
216            random_access: false,
217            sequential_scan: false,
218        }
219    }
220}
221
222impl<A: DataAccess> FileOptions<A> {
223    /// Ask for the right to read the data (`FILE_READ_DATA`). The options carry the access this adds
224    /// to what they already had: [`NoData`] becomes [`ReadOnly`](super::ReadOnly) and
225    /// [`WriteOnly`](super::WriteOnly) becomes [`ReadWrite`].
226    pub fn read(self) -> FileOptions<A::WithRead> {
227        self.with_access()
228    }
229
230    /// Ask for the right to write the data (`FILE_WRITE_DATA`), as [`read`](Self::read) asks to read.
231    pub fn write(self) -> FileOptions<A::WithWrite> {
232        self.with_access()
233    }
234
235    /// The rights to open with besides the data access (`dwDesiredAccess`): the attribute, delete and
236    /// security rights, which gate calls made on the handle this crate lends and not anything it
237    /// does itself. What the file's security allows is the kernel's to say, and its refusal reaches
238    /// the caller as its own error.
239    ///
240    /// Calling this again replaces the rights; join them with `|` to ask for several.
241    pub fn rights(mut self, rights: FileRights) -> Self {
242        self.rights = rights;
243        self
244    }
245
246    /// What other opens of the file may request while this handle is open (`dwShareMode`). The
247    /// default is [`ShareMode::ALL`]; [`ShareMode::NONE`] is an exclusive open, which a database that
248    /// owns its file wants.
249    ///
250    /// An open the sharing of either side does not allow fails with a [`SharingViolation`], whichever
251    /// of the two opens came first. Calling this again replaces the mode.
252    pub fn share(mut self, share: ShareMode) -> Self {
253        self.share = share;
254        self
255    }
256
257    fn with_access<A2: DataAccess>(self) -> FileOptions<A2> {
258        FileOptions {
259            access: PhantomData,
260            rights: self.rights,
261            share: self.share,
262            disposition: self.disposition,
263            open_reparse_point: self.open_reparse_point,
264            attributes: self.attributes,
265            delete_on_close: self.delete_on_close,
266            open_no_recall: self.open_no_recall,
267            posix_semantics: self.posix_semantics,
268            overlapped: self.overlapped,
269            random_access: self.random_access,
270            sequential_scan: self.sequential_scan,
271        }
272    }
273
274    /// What the open does about a file that is there and one that is not (`dwCreationDisposition`).
275    /// The default is [`Disposition::OpenExisting`]. Calling this again replaces the disposition.
276    ///
277    /// No disposition changes the contents of a file that exists, so none is refused for want of the
278    /// right to write. A caller that wants an existing file emptied does it through the handle this
279    /// crate lends, with `File::set_len(0)`, which needs the write right and is not atomic with the
280    /// open.
281    pub fn disposition(mut self, disposition: Disposition) -> Self {
282        self.disposition = disposition;
283        self
284    }
285
286    /// Open a link itself and not what it points to (`FILE_FLAG_OPEN_REPARSE_POINT`). Without it a
287    /// link is followed, including by [`Disposition::CreateNew`], which then creates the file the
288    /// link names. With it a link that is already there is what exists, so `CreateNew` fails and
289    /// `OpenAlways` opens the link. It has no effect on a path that is not a reparse point.
290    pub fn open_reparse_point(mut self, open_reparse_point: bool) -> Self {
291        self.open_reparse_point = open_reparse_point;
292        self
293    }
294
295    /// The attributes a file the open creates is given (`FILE_ATTRIBUTE_*` in `dwFlagsAndAttributes`).
296    /// The default is none, which is not `FILE_ATTRIBUTE_NORMAL`: the empty set passes no attribute at
297    /// all, and a created file has the attributes the file system gives it, `ARCHIVE` among them
298    /// (measured: the same as an open that passes `NORMAL`). Calling this again replaces the set.
299    ///
300    /// They are for a file that is created, and what an open of a file that exists does with them is
301    /// Win32's: the `CreateFileW` page says they are generally combined with the file's own or
302    /// ignored, and nothing here changes an existing file's attributes (measured: the open leaves them
303    /// as they were).
304    pub fn attributes(mut self, attributes: FileAttributes) -> Self {
305        self.attributes = attributes;
306        self
307    }
308
309    /// Delete the file when its last handle is closed (`FILE_FLAG_DELETE_ON_CLOSE`). A durability
310    /// report is then about data that does not outlive the handles, which is the caller's choice to
311    /// make.
312    ///
313    /// The file goes when the last handle to it, however that was opened, is closed. Measured on
314    /// Windows 11 build 26300: the open succeeds without the right to delete among the
315    /// [`rights`](Self::rights) (the `CreateFileW` pages do not say it is needed; `ZwCreateFile`'s
316    /// does, for its own flag, and that is not the call made here), and while the
317    /// handle is open a later open has to share deletion ([`ShareMode::DELETE`]) or it fails with a
318    /// [`SharingViolation`]. Nothing is added or checked here; which combinations are refused ahead
319    /// of the open is `DI-14.4`'s.
320    pub fn delete_on_close(mut self, delete_on_close: bool) -> Self {
321        self.delete_on_close = delete_on_close;
322        self
323    }
324
325    /// Ask that the file's data not be brought back from remote storage to be read
326    /// (`FILE_FLAG_OPEN_NO_RECALL`). The `CreateFileW` page says the flag is for remote storage
327    /// systems; it is passed on and nothing about a target depends on it. What it does to a file on
328    /// a local disk was not measured.
329    pub fn open_no_recall(mut self, open_no_recall: bool) -> Self {
330        self.open_no_recall = open_no_recall;
331        self
332    }
333
334    /// Access the file by POSIX rules (`FILE_FLAG_POSIX_SEMANTICS`). The `CreateFileW` page says
335    /// that this includes allowing names that differ only in case, and that a file made with the flag
336    /// may not be reachable by applications written for MS-DOS or 16-bit Windows. It is passed on and
337    /// nothing about a target depends on it.
338    pub fn posix_semantics(mut self, posix_semantics: bool) -> Self {
339        self.posix_semantics = posix_semantics;
340        self
341    }
342
343    /// Open the file for overlapped I/O (`FILE_FLAG_OVERLAPPED`). Every operation this crate's targets
344    /// make is addressed by offset, so the file pointer the system then stops keeping is not one they
345    /// read. Measured on Windows 11 build 26300 (the run is in `DI-D-64`), the ring takes such a
346    /// handle and reports the same result for each operation as for one opened without the flag, in
347    /// each buffering mode. What differs is when a completion is there: a handle opened without the
348    /// flag completes during the submit, and an unbuffered one opened with it was not complete then
349    /// for its reads, which then completed in an order that was not always the order queued.
350    ///
351    /// The handle this crate lends is the same one, so I/O a caller makes on it directly without an
352    /// offset, with a `std::fs::File` for one, is I/O on an overlapped handle, where the
353    /// `CreateFileW` page says the file pointer is not maintained and operations are not serialised.
354    pub fn overlapped(mut self, overlapped: bool) -> Self {
355        self.overlapped = overlapped;
356        self
357    }
358
359    /// Hint that the file will be accessed at random (`FILE_FLAG_RANDOM_ACCESS`), which the system
360    /// may use to tune its caching.
361    ///
362    /// It is passed to an unbuffered file as well as a cached one, and is never refused. The
363    /// `CreateFileW` documentation says the flag "has no effect if the file system does not support
364    /// cached I/O and `FILE_FLAG_NO_BUFFERING`", which can be read two ways, so what it does to an
365    /// unbuffered file is the kernel's to say. Measured on Windows 11 build 26300: every combination
366    /// of the two hints, cached and unbuffered, opens and carries a sector-aligned write and read,
367    /// and an unbuffered one also runs through the ring. Whether a hint changes the system's caching
368    /// was not measured, and nothing about the warranty class, the alignment or write-through
369    /// depends on it. Asking for this and [`sequential_scan`](Self::sequential_scan) together is
370    /// the documentation's "self-defeating", and is passed on all the same.
371    pub fn random_access(mut self, random_access: bool) -> Self {
372        self.random_access = random_access;
373        self
374    }
375
376    /// Hint that the file will be read sequentially from beginning to end
377    /// (`FILE_FLAG_SEQUENTIAL_SCAN`), which the system may use to tune its caching. The
378    /// documentation says it should not be used if reverse scans will be.
379    ///
380    /// Passed through and documented as [`random_access`](Self::random_access) is.
381    pub fn sequential_scan(mut self, sequential_scan: bool) -> Self {
382        self.sequential_scan = sequential_scan;
383        self
384    }
385
386    /// What this asks of `CreateFileW`, with `flags` added to the flags it implies. Every
387    /// combination of the options is a request, so nothing here is refused.
388    fn parameters(self, flags: u32) -> open_request::Parameters {
389        let reparse = if self.open_reparse_point {
390            FILE_FLAG_OPEN_REPARSE_POINT
391        } else {
392            0
393        };
394        let mut named = 0;
395        if self.delete_on_close {
396            named |= FILE_FLAG_DELETE_ON_CLOSE;
397        }
398        if self.open_no_recall {
399            named |= FILE_FLAG_OPEN_NO_RECALL;
400        }
401        if self.posix_semantics {
402            named |= FILE_FLAG_POSIX_SEMANTICS;
403        }
404        if self.overlapped {
405            named |= FILE_FLAG_OVERLAPPED;
406        }
407        let mut hints = 0;
408        if self.random_access {
409            hints |= FILE_FLAG_RANDOM_ACCESS;
410        }
411        if self.sequential_scan {
412            hints |= FILE_FLAG_SEQUENTIAL_SCAN;
413        }
414        open_request::Parameters {
415            access: data_rights::<A>() | self.rights.bits(),
416            disposition: self.disposition.bits(),
417            flags: flags | reparse | named | hints | self.attributes.bits(),
418            share: self.share.bits(),
419        }
420    }
421}
422
423/// What `A` asks `CreateFileW` for: `FILE_READ_DATA` as it reads and `FILE_WRITE_DATA` as it writes,
424/// and no other right. The append bit is never among them, so a handle that appends and cannot write
425/// at an offset cannot be asked for. The markers are the core's (DI-D-66); what they ask of Win32 is
426/// stated here.
427pub(super) const fn data_rights<A: DataAccess>() -> u32 {
428    (if A::READ { FILE_READ_DATA } else { 0 }) | (if A::WRITE { FILE_WRITE_DATA } else { 0 })
429}
430
431/// An unbuffered file was asked for on a device of another machine, which cannot keep the claim
432/// that makes a file unbuffered: what it reports is the remote system's, not the device's.
433#[derive(Clone, Debug, PartialEq, Eq)]
434pub struct RemoteDevice {
435    /// The path the file would have been opened at, resolved through any link or mapped drive.
436    pub location: PathBuf,
437}
438
439impl fmt::Display for RemoteDevice {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        write!(
442            f,
443            "{} is on a remote device, which cannot be opened unbuffered with the warranty that implies",
444            self.location.display()
445        )
446    }
447}
448
449impl std::error::Error for RemoteDevice {}
450
451/// An unbuffered file was asked for on a handle whose location could not be read: it has no final
452/// path (`NUL` has none), so it is not known to be a file on a local volume, which is what the
453/// warranty of an unbuffered file rests on.
454///
455/// This is this crate's own narrowing, not a condition `CreateFileW` documents: the open itself
456/// succeeded, and the handle is closed. A cached open of the same name is not refused. The call's
457/// own error is the [`source`](std::error::Error::source), kept as the call reported it, and is not
458/// a statement of what the handle is.
459#[derive(Debug)]
460pub struct UnknownLocation {
461    /// What reading the handle's final path reported.
462    pub cause: io::Error,
463}
464
465impl fmt::Display for UnknownLocation {
466    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467        write!(
468            f,
469            "the handle's final path could not be read, so it is not known to be a file on a local volume, which an unbuffered file needs: {}",
470            self.cause
471        )
472    }
473}
474
475impl std::error::Error for UnknownLocation {
476    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
477        Some(&self.cause)
478    }
479}
480
481/// An open failed because of how the file is already open: either this open asked for an access that
482/// an open already holding the file does not share, or it did not share an access that open asked for.
483/// `CreateFileW` fails both ways with `ERROR_SHARING_VIOLATION` and says nothing of which, and no
484/// documented call reports the other open's access or sharing, so this carries what *this* open asked
485/// for and nothing about the other.
486///
487/// It is the cause of an [`io::ErrorKind::ResourceBusy`] error from [`OpenedFile::open`], reached with
488/// [`io::Error::get_ref`] and a downcast, and of the same error from [`OpenedFile::open_by_id`]. The
489/// kernel's own error, which carries the Win32 code, is its [`source`](std::error::Error::source).
490#[derive(Debug)]
491pub struct SharingViolation {
492    /// What the open named the file by.
493    pub by: OpenedBy,
494    /// The data access the open asked for, or `None` for one that asked for no data right.
495    pub data: Option<Access>,
496    /// The rights the open asked for besides the data access.
497    pub rights: FileRights,
498    /// What this open allowed other opens to request.
499    pub share: ShareMode,
500    os_error: io::Error,
501}
502
503impl fmt::Display for SharingViolation {
504    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505        write!(
506            f,
507            "{} is open in a way that conflicts with this open (data access {}, {:?}, sharing {:?}): \
508             the other open does not share what this one asked for, or this one does not share what \
509             the other holds",
510            self.by,
511            self.data
512                .map_or_else(|| "none".to_owned(), |data| data.to_string()),
513            self.rights,
514            self.share,
515        )
516    }
517}
518
519impl std::error::Error for SharingViolation {
520    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
521        Some(&self.os_error)
522    }
523}
524
525/// What an open named the file by: the path it was given, the identifier it opened on the hint's
526/// volume, or, for a reopen, the handle it was given.
527#[derive(Clone, Debug, PartialEq, Eq)]
528pub enum OpenedBy {
529    /// A path, as it was given.
530    Path(PathBuf),
531    /// An identifier, by [`OpenedFile::open_by_id`].
532    Identifier(FileIdentifier),
533    /// What the original handle of a reopen names, by [`OpenedFile::reopen`]. The handle is the
534    /// caller's own, so it is not carried.
535    Reopen,
536}
537
538impl fmt::Display for OpenedBy {
539    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
540        match self {
541            Self::Path(path) => write!(f, "{}", path.display()),
542            Self::Identifier(identifier) => write!(f, "the file identified by {identifier:?}"),
543            Self::Reopen => write!(f, "the file the reopened handle names"),
544        }
545    }
546}
547
548/// The data access `A` asks for, as a value, or `None` for one that asks for no data right.
549pub(super) fn data_access<A: DataAccess>() -> Option<Access> {
550    match (A::READ, A::WRITE) {
551        (false, false) => None,
552        (true, false) => Some(Access::ReadOnly),
553        (false, true) => Some(Access::WriteOnly),
554        (true, true) => Some(Access::ReadWrite),
555    }
556}
557
558/// `error`, or the [`SharingViolation`] it is if the kernel said so.
559pub(super) fn refine_sharing(
560    error: io::Error,
561    path: &Wtf16Str,
562    data: Option<Access>,
563    rights: FileRights,
564    share: ShareMode,
565) -> io::Error {
566    refine_sharing_by(
567        error,
568        || OpenedBy::Path(PathBuf::from(path.to_os_string())),
569        data,
570        rights,
571        share,
572    )
573}
574
575/// `error`, or the [`SharingViolation`] it is if the kernel said so, naming the file as `by` does.
576fn refine_sharing_by(
577    error: io::Error,
578    by: impl FnOnce() -> OpenedBy,
579    data: Option<Access>,
580    rights: FileRights,
581    share: ShareMode,
582) -> io::Error {
583    if error.raw_os_error() != Some(ERROR_SHARING_VIOLATION.cast_signed()) {
584        return error;
585    }
586    io::Error::new(
587        io::ErrorKind::ResourceBusy,
588        SharingViolation {
589            by: by(),
590            data,
591            rights,
592            share,
593            os_error: error,
594        },
595    )
596}
597
598/// A file this crate opened, in buffering mode `B` and with data access `A`: a [`SharedHandle`] and
599/// what is known of it because of how it was opened. Cloning shares the one handle.
600///
601/// It is what a [`Win32FileTarget`](super::Win32FileTarget) is made from, and the only thing it
602/// can be made from; the module documentation says why. A target needs a data right, so a file
603/// opened with [`NoData`] cannot be made into one.
604///
605/// A consumer that needs the stronger warranty says so in its signature, and a cached file cannot
606/// be passed:
607///
608/// ```compile_fail,E0308
609/// use win_durable_ioring::Wtf16String;
610/// use win_durable_ioring::windows_backend::{Cached, FileOptions, OpenedFile, Unbuffered};
611///
612/// fn needs_an_unbuffered_file(_: OpenedFile<Unbuffered>) {}
613///
614/// let options = FileOptions::new().read().write();
615/// let name = Wtf16String::from_os_str("a-file".as_ref());
616/// let cached = OpenedFile::<Cached>::open(name, options).unwrap();
617/// needs_an_unbuffered_file(cached);
618/// ```
619///
620/// Nor can one opened for reading only be given where reading and writing are asked for:
621///
622/// ```compile_fail,E0308
623/// use win_durable_ioring::Wtf16String;
624/// use win_durable_ioring::windows_backend::{Cached, FileOptions, OpenedFile, ReadWrite};
625///
626/// fn needs_both(_: OpenedFile<Cached, ReadWrite>) {}
627///
628/// let name = Wtf16String::from_os_str("a-file".as_ref());
629/// let read_only = OpenedFile::<Cached, _>::open(name, FileOptions::new().read()).unwrap();
630/// needs_both(read_only);
631/// ```
632///
633/// The data access is a type parameter after the buffering mode, and a parameter written in a path
634/// takes its default, [`ReadWrite`], when it is left off, so a file opened with other options names it
635/// or leaves it to be inferred with `_`: `OpenedFile::<Cached, _>::open(path, options)`.
636#[derive(Clone, Debug)]
637pub struct OpenedFile<B: Buffering = Cached, A: DataAccess = ReadWrite> {
638    handle: SharedHandle,
639    geometry: Geometry,
640    mode: PhantomData<(B, A)>,
641}
642
643impl<B: Buffering, A: DataAccess> OpenedFile<B, A> {
644    /// Open `path` as `options` says, in buffering mode `B`: [`request`](Self::request) and then
645    /// [`perform`](OpenRequest::perform), one after the other on this thread. The handle has the data
646    /// access the options were given, which is its type `A`.
647    ///
648    /// **This blocks for as long as `CreateFileW` does**, which on a network path or an opportunistic
649    /// lock break can be a long time. A caller that cannot block builds the request here and performs
650    /// it on a thread it chooses.
651    ///
652    /// **Where the name leads is the caller's to know.** A name can be redirected to a device by a
653    /// reparse point in an intermediate directory, which `FILE_FLAG_OPEN_REPARSE_POINT` does not stop
654    /// (it applies to the final component), or by an object-manager link such as a drive letter, which
655    /// is resolved before a file system sees the name, so no reparse flag applies to it. An open of
656    /// a serial port's name was observed not to return. This crate does not inspect the name, refuse it,
657    /// bound the open or cancel it: the open is `CreateFileW`'s, and `CancelSynchronousIo` is documented
658    /// as a use and not as a guarantee, and targets a thread and not a call. The design notes' `DI-D-67`
659    /// has the sources and the measurement.
660    ///
661    /// # Errors
662    ///
663    /// Those of [`request`](Self::request) and of [`perform`](OpenRequest::perform).
664    pub fn open(path: Wtf16String, options: FileOptions<A>) -> io::Result<Self> {
665        Self::request(path, options)?.perform()
666    }
667
668    /// The request to open `path` as `options` says, in buffering mode `B`, built here and made
669    /// wherever the caller chooses with [`perform`](OpenRequest::perform). Nothing is opened, and
670    /// nothing blocks.
671    ///
672    /// The path is handed to Win32 as it was given: this crate does not lengthen it, shorten it or
673    /// refuse it, so a path longer than `MAX_PATH` without a verbatim prefix, and an empty one, are
674    /// what `CreateFileW` makes of them. A relative path is fixed to the current directory as it is
675    /// when this is called, so the request names the same file wherever and whenever it is performed.
676    /// Convert a `Path` with [`Wtf16String::from_os_str`], which loses nothing.
677    ///
678    /// # Errors
679    ///
680    /// [`io::ErrorKind::InvalidInput`] if `path` has an interior NUL, and the error of
681    /// `GetCurrentDirectoryW` if the current directory had to be read and could not be, both before
682    /// anything is touched.
683    pub fn request(path: Wtf16String, options: FileOptions<A>) -> io::Result<OpenRequest<B, A>> {
684        let (rights, share) = (options.rights, options.share);
685        let request = open_request::request(path, options.parameters(B::FLAGS))?;
686        Ok(OpenRequest {
687            request,
688            rights,
689            share,
690            mode: PhantomData,
691        })
692    }
693
694    /// The file's handle, owned and shared.
695    #[must_use]
696    pub fn handle(&self) -> &SharedHandle {
697        &self.handle
698    }
699
700    /// The alignment its operations need.
701    #[must_use]
702    pub fn geometry(&self) -> Geometry {
703        self.geometry
704    }
705}
706
707/// The request to open one file, built by [`OpenedFile::request`] and made by
708/// [`perform`](Self::perform): the path, fixed to the current directory if it was relative, and the
709/// options, typed by the buffering mode and the data access of the [`OpenedFile`] it opens.
710///
711/// It is `Send`, so it can be built on the thread that has the path and the current directory and
712/// performed on one that can afford to block, and it can be performed more than once, each time
713/// opening the file again.
714#[derive(Debug)]
715#[must_use = "a request that is not performed opens nothing"]
716pub struct OpenRequest<B: Buffering = Cached, A: DataAccess = ReadWrite> {
717    request: OpenFile,
718    rights: FileRights,
719    share: ShareMode,
720    mode: PhantomData<fn() -> (B, A)>,
721}
722
723impl<B: Buffering, A: DataAccess> OpenRequest<B, A> {
724    /// Give the file this open creates the security descriptor, built from SDDL with
725    /// [`SecurityDescriptor::from_sddl`]. The request owns it and frees it, and a caller never holds
726    /// a pointer to it. Calling this again replaces the descriptor.
727    ///
728    /// It is the request's and not a [`FileOptions`] field because a descriptor owns memory and
729    /// cannot be copied, and the options are a plain `Copy` description that is passed to several
730    /// opens. Handles are never inheritable: the attributes this passes to `CreateFileW` say so
731    /// (`bInheritHandle` is false), and a request given no descriptor passes none, which the
732    /// `CreateFileW` page documents as a handle that cannot be inherited.
733    ///
734    /// The `CreateFileW` page says the descriptor is for a file that is created and is ignored when
735    /// the file exists (measured: an open of an existing file with a descriptor that denies reading
736    /// leaves the file as it was). This crate does not apply the descriptor itself; a created
737    /// file's descriptor reads back as the one given, through `win-authorization-sys`'s
738    /// `get_security_info`, for a file opened with [`FileRights::READ_CONTROL`].
739    pub fn with_security_descriptor(self, descriptor: SecurityDescriptor) -> Self {
740        Self {
741            request: self
742                .request
743                .with_security(Some(SecurityAttributes::new(Some(descriptor), false))),
744            ..self
745        }
746    }
747
748    /// Open the file, on the calling thread. **This blocks for as long as `CreateFileW` does**, and
749    /// where the name leads is the caller's to know, as [`OpenedFile::open`] says.
750    ///
751    /// # Errors
752    ///
753    /// [`RemoteDevice`] (as an [`io::ErrorKind::Unsupported`] error) when `B` can claim its warranty
754    /// only on a local device and the opened handle is on a remote one. That is decided from the
755    /// handle's final path, which `GetFinalPathNameByHandleW` resolves through links and mapped
756    /// drives, so there is no window between the check and the open in which a link can be
757    /// swapped. It is made after the open, so a file created before the refusal stays created and
758    /// the open has already reached the network. [`UnknownLocation`] (also
759    /// [`io::ErrorKind::Unsupported`]) when the handle's final path cannot be read, since it is then
760    /// not known to be a file on a local volume; the call's own error is its source. That is this
761    /// crate's narrowing and not a condition of `CreateFileW`, and only an unbuffered open is subject
762    /// to it: a cached open of a device is not refused, and what a name leads to is the caller's
763    /// responsibility (DI-D-67). Otherwise whatever the open reports, among them [`SharingViolation`] (as an
764    /// [`io::ErrorKind::ResourceBusy`] error) when the file is open in a way that conflicts with
765    /// this open's access or sharing.
766    pub fn perform(&self) -> io::Result<OpenedFile<B, A>> {
767        let handle = open_request::perform(&self.request).map_err(|error| {
768            refine_sharing(
769                error,
770                self.request.path(),
771                data_access::<A>(),
772                self.rights,
773                self.share,
774            )
775        })?;
776        OpenedFile::admit(SharedHandle::from(handle))
777    }
778}
779
780impl<B: Buffering, A: DataAccess> OpenedFile<B, A> {
781    /// The checks every open makes on the handle it opened, whether by path or by identifier, and
782    /// the file it is if they pass (DI-14.5): when `B` claims its warranty only on a local device, the
783    /// handle's final path must be readable and must not name a device of another machine.
784    fn admit(handle: SharedHandle) -> io::Result<Self> {
785        if B::LOCAL_DEVICE_ONLY {
786            let location = final_location(&handle).map_err(|cause| {
787                io::Error::new(io::ErrorKind::Unsupported, UnknownLocation { cause })
788            })?;
789            if names_a_remote_device(&location) {
790                return Err(io::Error::new(
791                    io::ErrorKind::Unsupported,
792                    RemoteDevice { location },
793                ));
794            }
795        }
796        Ok(OpenedFile {
797            handle,
798            geometry: B::geometry(),
799            mode: PhantomData,
800        })
801    }
802}
803
804impl<A: DataAccess> OpenedFile<Unbuffered, A> {
805    /// State the device's sector size, for a device whose sectors are smaller than
806    /// [`Unbuffered::DEFAULT_SECTOR_SIZE`]. A size that is too small for the device is refused by
807    /// the device on the first transfer that needs more, as an error on that operation's
808    /// completion, never as silent damage.
809    ///
810    /// # Errors
811    ///
812    /// [`GeometryError`] if `sector_size` is not a power of two.
813    pub fn with_sector_size(mut self, sector_size: u32) -> Result<Self, GeometryError> {
814        self.geometry = Geometry::uniform(sector_size)?;
815        Ok(self)
816    }
817}
818
819impl<B: Buffering, A: DataAccess> AsHandle for OpenedFile<B, A> {
820    fn as_handle(&self) -> BorrowedHandle<'_> {
821        self.handle.as_handle()
822    }
823}
824
825impl<B: Buffering, A: DataAccess> AsRawHandle for OpenedFile<B, A> {
826    fn as_raw_handle(&self) -> RawHandle {
827        self.handle.as_raw_handle()
828    }
829}
830
831/// Where an opened handle is: its final path, in `GetFinalPathNameByHandleW`'s default form, which
832/// follows links and mapped drives to the object the handle names. A handle opened on a link itself
833/// (`FILE_FLAG_OPEN_REPARSE_POINT`) names the link, on its own volume.
834///
835/// The request is given the handle shared, not a duplicate of it (DI-16.1).
836fn final_location(handle: &SharedHandle) -> io::Result<PathBuf> {
837    let path = QueryFinalPath::new(handle.clone())
838        .perform()
839        .map_err(|error| match error {
840            FinalPathError::Win32(code) => {
841                io::Error::from_raw_os_error(open_request::raw_code(code.code()))
842            }
843            other => io::Error::other(other),
844        })?;
845    Ok(PathBuf::from(path.to_os_string()))
846}
847
848/// Whether a resolved path names a device of another machine: a UNC share, in either of its
849/// forms. A mapped drive and a link to a share resolve to one, so they are seen as well.
850fn names_a_remote_device(location: &Path) -> bool {
851    matches!(
852        location.components().next(),
853        Some(Component::Prefix(prefix))
854            if matches!(prefix.kind(), Prefix::UNC(..) | Prefix::VerbatimUNC(..))
855    )
856}