Skip to main content

win_durable_ioring/windows_backend/
file_target.rs

1// Copyright (c) 2026 Mike Grier
2//! [`Win32FileTarget`]: a target backed by a Win32 file, a raw volume or a whole disk (DI-D-48,
3//! DI-D-50, DI-D-60, DI-D-61, DI-D-62).
4
5use win_shared_os_owned_handle::SharedHandle;
6
7use super::opened_disk::OpenedDisk;
8use super::opened_file::{Buffering, OpenedFile, Unbuffered};
9use super::opened_volume::OpenedVolume;
10use durable_ioring::FlushDomain;
11use durable_ioring::{Access, Capabilities, Geometry, HasData, IoTarget, WarrantyClass};
12
13/// A target backed by a Win32 file, a raw volume or a whole disk this crate opened, held as a
14/// `SharedHandle` -- an owning form, never a raw handle (DI-D-4).
15///
16/// It is made from an [`OpenedFile`], an [`OpenedVolume`] or an [`OpenedDisk`] and from nothing else, and takes its
17/// warranty class, its alignment and its capabilities from how that was opened, not from a
18/// declaration (DI-D-60): one opened cached is [`WarrantyClass::BestEffort`], and one opened
19/// [`Unbuffered`](super::Unbuffered) is [`WarrantyClass::SharedDevice`], needs a sector's alignment,
20/// and offers per-write write-through. [`WarrantyClass::Complete`] is never derived from how a file
21/// was opened: it needs knowledge of what else writes the device, which a volume's lock does not give
22/// (DI-D-61). A consumer who has that knowledge says so through
23/// [`new_attested_exclusive`](Self::new_attested_exclusive),
24/// [`from_volume_attested_exclusive`](Self::from_volume_attested_exclusive) or
25/// [`from_disk_attested_exclusive`](Self::from_disk_attested_exclusive) (DI-D-68).
26///
27/// A target needs a data right, so it is made only from a handle whose data access is [`HasData`]: one
28/// opened with [`NoData`](super::NoData) is refused when the program is compiled, not when the
29/// first operation fails.
30///
31/// ```compile_fail,E0277
32/// use win_durable_ioring::Wtf16String;
33/// use win_durable_ioring::windows_backend::{Cached, FileOptions, OpenedFile, Win32FileTarget};
34///
35/// let name = Wtf16String::from_os_str("a-file".as_ref());
36/// let no_data = OpenedFile::<Cached, _>::open(name, FileOptions::new()).unwrap();
37/// let _ = Win32FileTarget::new(no_data);
38/// ```
39///
40/// A volume's flush domains are found from its disks. A file's are what it cannot know, so they are
41/// declared with [`with_domains`](Self::with_domains); declaring them on a volume adds to the ones
42/// found, and never removes one.
43#[derive(Clone, Debug)]
44pub struct Win32FileTarget {
45    handle: SharedHandle,
46    /// What the backing found for itself: a volume's disks.
47    found: Vec<FlushDomain>,
48    /// What the consumer declared.
49    declared: Vec<FlushDomain>,
50    /// Both, each once: what the target reports.
51    domains: Vec<FlushDomain>,
52    warranty: WarrantyClass,
53    geometry: Geometry,
54    capabilities: Capabilities,
55    /// What the handle was opened for, stated to the core (DI-D-66).
56    access: Access,
57}
58
59impl Win32FileTarget {
60    /// The target `file` makes, claiming what its open mode allows and no more.
61    #[must_use]
62    pub fn new<B: Buffering, A: HasData>(file: OpenedFile<B, A>) -> Self {
63        Self {
64            geometry: file.geometry(),
65            handle: file.handle().clone(),
66            found: Vec::new(),
67            declared: Vec::new(),
68            domains: Vec::new(),
69            warranty: B::WARRANTY,
70            capabilities: B::CAPABILITIES,
71            access: A::ACCESS,
72        }
73    }
74
75    /// The target `volume` makes: its sector's alignment, the flush domains of the disks it sits
76    /// on, and what its open mode allows and no more.
77    #[must_use]
78    pub fn from_volume<B: Buffering, A: HasData>(volume: OpenedVolume<B, A>) -> Self {
79        let found = volume.flush_domains();
80        Self {
81            geometry: volume.geometry(),
82            handle: volume.handle().clone(),
83            domains: found.clone(),
84            found,
85            declared: Vec::new(),
86            warranty: B::WARRANTY,
87            capabilities: B::CAPABILITIES,
88            access: A::ACCESS,
89        }
90    }
91
92    /// The target `disk` makes: its sector's alignment, the disk itself as its flush domain, and
93    /// what its open mode allows and no more.
94    #[must_use]
95    pub fn from_disk<B: Buffering, A: HasData>(disk: OpenedDisk<B, A>) -> Self {
96        let found = vec![disk.flush_domain()];
97        Self {
98            geometry: disk.geometry(),
99            handle: disk.handle().clone(),
100            domains: found.clone(),
101            found,
102            declared: Vec::new(),
103            warranty: B::WARRANTY,
104            capabilities: B::CAPABILITIES,
105            access: A::ACCESS,
106        }
107    }
108
109    /// The target `file` makes, with the consumer's attestation that nothing else writes the device it
110    /// is on and that the flush domains it reports are all of them, so that it claims
111    /// [`WarrantyClass::Complete`] (DI-D-68).
112    ///
113    /// The claim is the consumer's and nothing here can check it: a handle cannot show what else writes
114    /// a device. A wrong claim mislabels what the target reports and changes nothing the layer does,
115    /// which behaves the same for every class (DI-D-50 P3). The flush domains, the alignment and the
116    /// capabilities are what [`new`](Self::new) gives the same file.
117    ///
118    /// Only a file opened [`Unbuffered`] can be attested. A cached file claims
119    /// [`WarrantyClass::BestEffort`] because the cache may report success for data already lost, and
120    /// what else writes the device does not change that, so there is nothing to attest; it is refused
121    /// when the program is compiled.
122    ///
123    /// ```compile_fail,E0308
124    /// use win_durable_ioring::Wtf16String;
125    /// use win_durable_ioring::windows_backend::{
126    ///     Cached, FileOptions, OpenedFile, Win32FileTarget,
127    /// };
128    ///
129    /// let name = Wtf16String::from_os_str("a-file".as_ref());
130    /// let cached = OpenedFile::<Cached, _>::open(name, FileOptions::new().read().write()).unwrap();
131    /// let _ = Win32FileTarget::new_attested_exclusive(cached);
132    /// ```
133    #[must_use]
134    pub fn new_attested_exclusive<A: HasData>(file: OpenedFile<Unbuffered, A>) -> Self {
135        Self::new(file).attested_exclusive()
136    }
137
138    /// The target `volume` makes, with the consumer's attestation that nothing else writes the disks it
139    /// sits on and that the flush domains it reports, which are those disks, are all of them, so that
140    /// it claims [`WarrantyClass::Complete`]. The claim, what it does not change and why a cached
141    /// volume cannot make it are those of [`new_attested_exclusive`](Self::new_attested_exclusive).
142    ///
143    /// A volume's lock keeps other handles off the volume, not other writers off its disks
144    /// (DI-D-61), so a locked volume is not thereby attested.
145    #[must_use]
146    pub fn from_volume_attested_exclusive<A: HasData>(volume: OpenedVolume<Unbuffered, A>) -> Self {
147        Self::from_volume(volume).attested_exclusive()
148    }
149
150    /// The target `disk` makes, with the consumer's attestation that nothing else writes the disk and
151    /// that the flush domain it reports, the disk itself, is all of them, so that it claims
152    /// [`WarrantyClass::Complete`]. The claim, what it does not change and why a cached disk cannot
153    /// make it are those of [`new_attested_exclusive`](Self::new_attested_exclusive).
154    #[must_use]
155    pub fn from_disk_attested_exclusive<A: HasData>(disk: OpenedDisk<Unbuffered, A>) -> Self {
156        Self::from_disk(disk).attested_exclusive()
157    }
158
159    /// The one place the attested claim is made.
160    fn attested_exclusive(mut self) -> Self {
161        self.warranty = WarrantyClass::Complete;
162        self
163    }
164
165    /// Declare every flush domain its writes depend on, beyond any the backing found for itself.
166    /// Declaring again replaces what was declared before, and never what the backing found.
167    #[must_use]
168    pub fn with_domains(mut self, domains: impl IntoIterator<Item = FlushDomain>) -> Self {
169        self.declared = domains.into_iter().collect();
170        self.domains = self.found.clone();
171        for domain in &self.declared {
172            if !self.domains.contains(domain) {
173                self.domains.push(domain.clone());
174            }
175        }
176        self
177    }
178
179    /// The file's handle.
180    #[must_use]
181    pub fn handle(&self) -> &SharedHandle {
182        &self.handle
183    }
184
185    /// State an access the handle was not opened with, to test what happens when a target
186    /// overstates it: the core trusts what a target states, so the kernel's own refusal is what is
187    /// left to meet.
188    #[cfg(test)]
189    #[must_use]
190    pub(crate) fn claiming(mut self, access: Access) -> Self {
191        self.access = access;
192        self
193    }
194
195    /// State a class a file's open mode cannot give, to test what the backend does with each.
196    #[cfg(test)]
197    #[must_use]
198    pub(crate) fn declared(
199        mut self,
200        warranty: WarrantyClass,
201        geometry: Geometry,
202        capabilities: Capabilities,
203    ) -> Self {
204        self.warranty = warranty;
205        self.geometry = geometry;
206        self.capabilities = capabilities;
207        self
208    }
209}
210
211impl IoTarget for Win32FileTarget {
212    fn flush_domains(&self) -> &[FlushDomain] {
213        &self.domains
214    }
215
216    fn warranty(&self) -> WarrantyClass {
217        self.warranty
218    }
219
220    fn geometry(&self) -> Geometry {
221        self.geometry
222    }
223
224    fn capabilities(&self) -> Capabilities {
225        self.capabilities
226    }
227
228    fn access(&self) -> Access {
229        self.access
230    }
231}