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}