fs_core/block.rs
1//! Block-device traits.
2//!
3//! Two layers because not every consumer needs writes:
4//!
5//! - [`BlockRead`] is the minimum: positioned reads + size. Disk-image
6//! readers (qcow2 reading) and probes (partition table walk) only need
7//! this much.
8//! - [`BlockDevice`] extends `BlockRead` with optional `write_at` / `flush`
9//! / `is_writable` / `set_len` / `can_grow`. Read-only devices that opt
10//! into the larger trait inherit the default `Err(ReadOnly)` write path
11//! automatically, and every device that cannot change its own length
12//! inherits the same refusal for `set_len`.
13//!
14//! Both traits are `Send + Sync` so callers can hold them behind `Arc<dyn _>`
15//! across thread boundaries.
16
17use crate::error::{Error, Result};
18
19/// Read-only random-access block device.
20pub trait BlockRead: Send + Sync {
21 /// Read exactly `buf.len()` bytes starting at `offset` (bytes from the
22 /// start of the device).
23 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()>;
24
25 /// Total device size in bytes. Used for bounds checks.
26 ///
27 /// # It must not change for the life of the device — except through
28 /// [`BlockDevice::set_len`]
29 ///
30 /// Callers are entitled to read it once, act on it, and read it again
31 /// later expecting the same answer — [`crate::CachingDevice`] does
32 /// exactly that, bounding a read against it and then clamping each
33 /// block it fetches against it. A device whose size moves between the
34 /// two makes the cache hold a block shorter than the read it was
35 /// fetched for.
36 ///
37 /// An implementation whose length changes UNDERNEATH IT — a second
38 /// handle appending to the same file, a volume grown by something
39 /// else — must still not follow it. Reopen instead. That is
40 /// rust-fs-core#70, and `tests/size_stability_contract.rs` measures
41 /// it: a `FileDevice` whose backing file grows through another handle
42 /// goes on reporting the length it was opened with.
43 ///
44 /// # THE ONE DOOR, AND WHY IT IS NOT A HOLE IN THE CONTRACT
45 ///
46 /// [`BlockDevice::set_len`] moves this number, and it is the only
47 /// thing that may. The distinction is not "the size is stable except
48 /// when it is not": it is that a caller ASKED, so the moment the
49 /// number moves is a moment the caller chose, on a call it made, and
50 /// every wrapper on the path is given the chance to keep its own view
51 /// consistent as it goes past. `size_bytes` never surprises a reader;
52 /// `set_len` is the reader.
53 ///
54 /// That is exactly what a device following its backing file could not
55 /// offer. Nobody is told, nothing is invalidated, and the two halves
56 /// of one device end up disagreeing about where it ends — which is
57 /// the defect, not the movement.
58 ///
59 /// A device that answers `false` to [`BlockDevice::can_grow`] cannot
60 /// be moved at all, and the stronger, older reading of this contract
61 /// holds for it unchanged. Most devices in this crate are in that
62 /// class.
63 ///
64 /// # What the implementations here actually do
65 ///
66 /// This used to say "every implementation in this crate takes its size
67 /// once, at construction". That was not true of any of the three
68 /// shapes below, and it is the sentence an implementer in a sibling
69 /// crate reads before deciding their own device may report a live
70 /// length. [`crate::caching_device`]'s resizing path and
71 /// `tests/caching_size_change.rs` both used to describe this
72 /// differently again, so the crate said three incompatible things
73 /// about one contract.
74 ///
75 /// - [`crate::FileDevice`] and the slice devices in
76 /// [`crate::slice`] do take their size once, at construction. The
77 /// slices keep it forever; a `FileDevice` opened read-write on a
78 /// regular file also answers [`BlockDevice::can_grow`] with `true`,
79 /// so its number moves when — and only when — somebody calls
80 /// [`BlockDevice::set_len`] on it.
81 /// - [`crate::ReadOnlyDevice`], [`crate::CountingDevice`] and
82 /// [`crate::CachingDevice`] FORWARD the question to the device they
83 /// wrap, on every call, and so are exactly as stable as it is. They
84 /// cannot be more: a wrapper has no way to hold a moving device
85 /// still. The `Arc<T>`, `Box<T>` and `&T` blanket impls below
86 /// forward the same way.
87 /// - [`crate::CallbackDevice`] keeps its size in a PUBLIC field, so
88 /// nothing stops a caller moving it after construction. Stability
89 /// there is the caller's to keep, not the type's to enforce.
90 ///
91 /// So the contract binds the implementer; it is not something this
92 /// crate's types guarantee on their behalf. `tests/
93 /// size_stability_contract.rs` tests each of these behaviours rather
94 /// than leaving this paragraph to be believed.
95 fn size_bytes(&self) -> u64;
96}
97
98/// Read-write random-access block device. Implementors that genuinely
99/// support writes override `write_at` / `flush` / `is_writable`. The
100/// defaults model a strict read-only device.
101pub trait BlockDevice: BlockRead {
102 /// Write exactly `buf.len()` bytes at `offset`. Default: returns
103 /// [`Error::ReadOnly`].
104 fn write_at(&self, _offset: u64, _buf: &[u8]) -> Result<()> {
105 Err(Error::ReadOnly)
106 }
107
108 /// Flush pending writes to stable storage. No-op by default.
109 fn flush(&self) -> Result<()> {
110 Ok(())
111 }
112
113 /// Whether `write_at` is likely to succeed. Mount paths use this to
114 /// decide whether to attempt journal replay or stay strict-read-only.
115 fn is_writable(&self) -> bool {
116 false
117 }
118
119 /// Set the device's length to `new_len`. Default: returns
120 /// [`Error::ReadOnly`].
121 ///
122 /// # WHY A DEVICE NEEDS THIS AT ALL
123 ///
124 /// Every sparse disk-image format in this family allocates the same
125 /// way: append a block, cluster or grain to the end of the file, then
126 /// record where it went. The only tool any of them had for the first
127 /// half was a write past the end, and #75 refused that — correctly.
128 /// `write_all` at a seeked offset EXTENDS a file, so the backing store
129 /// grew while `size_bytes` went on reporting its construction-time
130 /// length, and a caller bounding its reads by `size_bytes` (which is
131 /// what [`crate::CachingDevice`] does, clamping every block it
132 /// fetches) could never reach the bytes it had just written. The two
133 /// halves of one device disagreed about where it ended. That is
134 /// rust-fs-core#70.
135 ///
136 /// The bound is right and it stays. What was missing is the other
137 /// half: a way to say *make the device longer*, out loud, so the
138 /// number it reports and everything caching it move at the same
139 /// moment. See rust-fs-core#147 and #129 for the four crates this
140 /// blocked, and the measurements that named them.
141 ///
142 /// # THE CONTRACT IS THE ATOMICITY, NOT THE SIGNATURE
143 ///
144 /// An implementation must leave the backing store, the number
145 /// [`BlockRead::size_bytes`] reports, and any cached view of the
146 /// device agreeing with each other when this returns. An
147 /// implementation that extends the store and leaves `size_bytes`
148 /// stale, or leaves a cache holding a block clamped to the old
149 /// length, has re-created #70 — and it will pass a naive test,
150 /// because the write it enables succeeds and only a LATER cached read
151 /// finds the hole.
152 ///
153 /// Where the two steps cannot be made one instruction, publish the
154 /// NARROWER of the two lengths first: the declared size may lag
155 /// behind a store that has already grown, but it must never exceed
156 /// one that has already shrunk. A read inside a device that is
157 /// shorter than it claims is a read off the end of the store.
158 ///
159 /// # IT SETS, IT DOES NOT ONLY GROW
160 ///
161 /// `new_len` below the current length TRUNCATES, discarding the bytes
162 /// past it, exactly as [`std::fs::File::set_len`] does — the name is
163 /// that method's and it means the same thing. A caller that only ever
164 /// appends should compute `new_len` from `size_bytes()` and never
165 /// hand this a smaller number; hiding the shrink behind a refusal
166 /// would make a method named `set_len` do something else.
167 ///
168 /// # WHY THIS IS ON `BlockDevice` AND NOT A `GrowableDevice` TRAIT
169 ///
170 /// Because of what the consumers hold. All four image crates carry
171 /// `Arc<dyn BlockDevice>` — `rust-img-vhdx` at 15 sites,
172 /// `rust-img-qcow2` at 7, `rust-img-vhd` at 6, `rust-img-vmdk` at 5 —
173 /// and a `dyn` type cannot be bounded onto a second trait without
174 /// downcasting through [`std::any::Any`]. That turns "this device
175 /// cannot grow" from a value into a failed downcast: a runtime error
176 /// with a worse message than the one the default below gives, at
177 /// every one of those 33 sites.
178 ///
179 /// It is also the idiom this trait already uses. `write_at` defaults
180 /// to `Err(Error::ReadOnly)` and `is_writable` defaults to `false`
181 /// for exactly the same reason — an optional capability, answered by
182 /// the device rather than by the type system. [`can_grow`] is
183 /// `is_writable` for length, and it is asked the same way.
184 ///
185 /// [`can_grow`]: BlockDevice::can_grow
186 ///
187 /// # THE DEFAULT REFUSES, AND IT IS `ReadOnly` DELIBERATELY
188 ///
189 /// Same variant as `write_at`'s default, because it is the same
190 /// statement one field over: this device will not accept a mutation
191 /// of this kind. [`crate::stream`] maps `ReadOnly` to
192 /// `PermissionDenied` and `Custom` to an uncategorised
193 /// `io::Error::other`, so a caller branching on the former to say
194 /// "this cannot be modified" keeps working over a refused grow.
195 ///
196 /// A device that is writable but fixed-length — a block device node,
197 /// a slice — is the one place that reads oddly, and it is why
198 /// [`can_grow`] exists: branch on the capability, not on the error.
199 fn set_len(&self, _new_len: u64) -> Result<()> {
200 Err(Error::ReadOnly)
201 }
202
203 /// Whether [`set_len`] is likely to succeed. Default: `false`.
204 ///
205 /// The question an image writer asks before it plans an allocation,
206 /// and the reason the default above can be a refusal rather than a
207 /// panic or a downcast. `is_writable` for length.
208 ///
209 /// `false` here is a promise that `set_len` will refuse, not a hint.
210 /// A caller is entitled to read it once at mount time and build a
211 /// writer around the answer.
212 ///
213 /// [`set_len`]: BlockDevice::set_len
214 fn can_grow(&self) -> bool {
215 false
216 }
217}
218
219// Forwarding impls so `Arc<T>` and `Box<T>` work transparently as
220// `&dyn BlockRead` / `&dyn BlockDevice`.
221//
222// EVERY DEFAULTED METHOD ON `BlockDevice` HAS TO BE FORWARDED HERE, AND
223// A MISSING ONE IS SILENT.
224//
225// Method resolution on an `Arc<dyn BlockDevice>` finds the impl below
226// BEFORE it derefs to the device inside, so a method these impls do not
227// override is answered by the TRAIT DEFAULT over a device that would
228// have answered differently. Nothing warns: the code compiles, the type
229// is right, and the wrapper quietly says `false` / `Err(ReadOnly)` for a
230// device that can do the thing.
231//
232// That is not hypothetical for `set_len`. The four image crates hold
233// `Arc<dyn BlockDevice>` at 33 sites between them, so a `set_len` added
234// to the trait and not added here would have shipped an API that changed
235// nothing for any of them. `tests/device_growth.rs` pins both directions
236// through both wrappers — forwarding the capability AND forwarding a
237// refusal, since an impl that answered `true` unconditionally would
238// satisfy the first alone.
239
240impl<T: BlockRead + ?Sized> BlockRead for std::sync::Arc<T> {
241 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
242 (**self).read_at(offset, buf)
243 }
244 fn size_bytes(&self) -> u64 {
245 (**self).size_bytes()
246 }
247}
248
249impl<T: BlockDevice + ?Sized> BlockDevice for std::sync::Arc<T> {
250 fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
251 (**self).write_at(offset, buf)
252 }
253 fn flush(&self) -> Result<()> {
254 (**self).flush()
255 }
256 fn is_writable(&self) -> bool {
257 (**self).is_writable()
258 }
259 fn set_len(&self, new_len: u64) -> Result<()> {
260 (**self).set_len(new_len)
261 }
262 fn can_grow(&self) -> bool {
263 (**self).can_grow()
264 }
265}
266
267impl<T: BlockRead + ?Sized> BlockRead for Box<T> {
268 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
269 (**self).read_at(offset, buf)
270 }
271 fn size_bytes(&self) -> u64 {
272 (**self).size_bytes()
273 }
274}
275
276impl<T: BlockRead + ?Sized> BlockRead for &T {
277 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
278 (**self).read_at(offset, buf)
279 }
280 fn size_bytes(&self) -> u64 {
281 (**self).size_bytes()
282 }
283}
284
285impl<T: BlockDevice + ?Sized> BlockDevice for Box<T> {
286 fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
287 (**self).write_at(offset, buf)
288 }
289 fn flush(&self) -> Result<()> {
290 (**self).flush()
291 }
292 fn is_writable(&self) -> bool {
293 (**self).is_writable()
294 }
295 fn set_len(&self, new_len: u64) -> Result<()> {
296 (**self).set_len(new_len)
297 }
298 fn can_grow(&self) -> bool {
299 (**self).can_grow()
300 }
301}