Skip to main content

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}