Skip to main content

fs_core/
slice.rs

1//! Slice adapters — view a byte sub-range of any `BlockRead` as its own
2//! device. Useful any time you want to feed a fragment of a larger
3//! device to a consumer that expects a whole block source — partition
4//! probes, image-file extents, mmap-style views, fuzzer harnesses.
5//!
6//! Three variants:
7//!
8//! - [`SliceReader`] borrows the parent, lifetime-tied. Cheaper when the
9//!   parent outlives the slice and you can express that statically.
10//! - [`OwnedSlice`] holds an `Arc` to the parent. Use when the parent's
11//!   lifetime can't be expressed in a borrow (FFI handles, slice handed
12//!   across thread boundaries, etc.).
13//! - [`OwnedRwSlice`] holds an `Arc<dyn BlockDevice>` and propagates
14//!   writes to the parent.
15//!
16//! The first two are strictly read-only: the default `Err(ReadOnly)`
17//! write path from [`BlockDevice`] applies.
18//!
19//! # Which error an out-of-range request gets
20//!
21//! All three share one range check — `SliceGeometry::rebase` — and it
22//! answers in two different currencies depending on the direction of the
23//! request:
24//!
25//! | request outside `[0, length)` | error |
26//! |---|---|
27//! | read  | [`Error::ShortRead`] with `got: 0` |
28//! | write | [`Error::OutOfBounds`] |
29//!
30//! The asymmetry is deliberate. A slice exists to be substitutable for a
31//! real device of size `length`, and a real device — [`FileDevice`] —
32//! answers a read that begins at or past its end with exactly
33//! `ShortRead { offset, want, got: 0 }`. A slice that answered
34//! `OutOfBounds` would be distinguishable from the thing it stands in
35//! for, and every caller that already handles end-of-device would need a
36//! second arm to cope with slices. Writes have no partial-write variant
37//! to stay consistent with, and a caller that overran a write needs the
38//! device size in order to clamp and retry — which is what
39//! [`Error::OutOfBounds`] carries and [`Error::ShortRead`] does not.
40//!
41//! The match is on the variant, not on `got`. A slice refuses an
42//! out-of-range read before it touches the parent, so it reports `got: 0`
43//! and leaves the buffer untouched — including for a read that begins
44//! inside the slice and runs off its end, where [`FileDevice`] would have
45//! copied the readable prefix and reported its length. `got` counts bytes
46//! actually delivered, and a slice delivers none.
47//!
48//! This governs the slice's own range only. A request that *is* inside
49//! `[0, length)` is forwarded to the parent, and whatever the parent says
50//! about it — including [`Error::OutOfBounds`] from a container reader
51//! that knows its virtual size — comes back unchanged.
52//!
53//! [`FileDevice`]: crate::FileDevice
54
55use crate::block::{BlockDevice, BlockRead};
56use crate::error::{Error, Result};
57use std::sync::Arc;
58
59/// Where a slice sits on its parent, and the one bounds rule the three
60/// slice types share.
61///
62/// The public slice types differ only in how they hold the parent and
63/// whether writes propagate. The geometry, the range check and the choice
64/// of error are identical across all of them, so they live here — one
65/// definition to read, one place to change.
66#[derive(Clone, Copy)]
67struct SliceGeometry {
68    start: u64,
69    length: u64,
70}
71
72impl SliceGeometry {
73    fn new(start: u64, length: u64) -> Self {
74        Self { start, length }
75    }
76
77    /// Parent offset corresponding to `offset`, or `None` when
78    /// `[offset, offset + len)` is not wholly inside `[0, length)`. An
79    /// `offset + len` that overflows `u64` counts as outside.
80    ///
81    /// `start + offset` is deliberately unchecked. A slice is built from
82    /// a parent's real geometry, so `start + length` is assumed to fit in
83    /// a `u64`; the constructors do not validate that, and a slice built
84    /// with a nonsense `start` overflows here rather than quietly reading
85    /// some other part of the parent.
86    fn rebase(&self, offset: u64, len: u64) -> Option<u64> {
87        let end = offset.checked_add(len)?;
88        if end > self.length {
89            return None;
90        }
91        Some(self.start + offset)
92    }
93
94    /// Bounds-check a read and rebase it onto the parent.
95    ///
96    /// Out of range is [`Error::ShortRead`] with `got: 0` — the same
97    /// answer a real device of size `length` gives for a read beginning
98    /// at or past its end. See the module docs for why.
99    fn rebase_read(&self, offset: u64, len: usize) -> Result<u64> {
100        self.rebase(offset, len as u64).ok_or(Error::ShortRead {
101            offset,
102            want: len,
103            got: 0,
104        })
105    }
106
107    /// Bounds-check a write and rebase it onto the parent.
108    ///
109    /// Out of range is [`Error::OutOfBounds`]: nothing was written, and
110    /// the caller is handed the slice's size so it can clamp and retry.
111    fn rebase_write(&self, offset: u64, len: usize) -> Result<u64> {
112        self.rebase(offset, len as u64).ok_or(Error::OutOfBounds {
113            offset,
114            len: len as u64,
115            size: self.length,
116        })
117    }
118}
119
120/// Borrowed slice of a parent `BlockRead`.
121///
122/// `read_at(0, …)` reads `start` of the parent. Reads outside
123/// `[0, length)` return [`Error::ShortRead`] with `got: 0`.
124pub struct SliceReader<'a> {
125    parent: &'a (dyn BlockRead + 'a),
126    geom: SliceGeometry,
127}
128
129impl<'a> SliceReader<'a> {
130    pub fn new(parent: &'a (dyn BlockRead + 'a), start: u64, length: u64) -> Self {
131        Self {
132            parent,
133            geom: SliceGeometry::new(start, length),
134        }
135    }
136
137    /// Byte offset of this slice on the parent device.
138    pub fn start(&self) -> u64 {
139        self.geom.start
140    }
141
142    /// Length of this slice in bytes (== `size_bytes()`).
143    pub fn length(&self) -> u64 {
144        self.geom.length
145    }
146}
147
148impl<'a> BlockRead for SliceReader<'a> {
149    fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
150        let at = self.geom.rebase_read(offset, buf.len())?;
151        self.parent.read_at(at, buf)
152    }
153
154    fn size_bytes(&self) -> u64 {
155        self.geom.length
156    }
157}
158
159/// Slices are read-only by default — even where the parent is writable,
160/// slicing is almost always paired with a read-only inspection or
161/// dispatch workflow.
162impl<'a> BlockDevice for SliceReader<'a> {}
163
164/// Owned slice over an `Arc<dyn BlockRead>`. Use when the parent's
165/// lifetime can't be expressed in a borrow — e.g. when the slice is
166/// handed across an FFI boundary or stored in a long-lived struct.
167///
168/// Reads outside `[0, length)` return [`Error::ShortRead`] with `got: 0`.
169pub struct OwnedSlice {
170    parent: Arc<dyn BlockRead>,
171    geom: SliceGeometry,
172}
173
174impl OwnedSlice {
175    pub fn new(parent: Arc<dyn BlockRead>, start: u64, length: u64) -> Self {
176        Self {
177            parent,
178            geom: SliceGeometry::new(start, length),
179        }
180    }
181
182    /// Byte offset of this slice on the parent device.
183    pub fn start(&self) -> u64 {
184        self.geom.start
185    }
186
187    /// Length of this slice in bytes (== `size_bytes()`).
188    pub fn length(&self) -> u64 {
189        self.geom.length
190    }
191}
192
193impl BlockRead for OwnedSlice {
194    fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
195        let at = self.geom.rebase_read(offset, buf.len())?;
196        self.parent.read_at(at, buf)
197    }
198
199    fn size_bytes(&self) -> u64 {
200        self.geom.length
201    }
202}
203
204/// Same rationale as `SliceReader`: read-only by default.
205impl BlockDevice for OwnedSlice {}
206
207/// Owned, read-WRITE slice over an `Arc<dyn BlockDevice>`. Use when the
208/// parent is writable and the slice should propagate writes (e.g. an
209/// individual partition handed to a filesystem driver).
210///
211/// Reads outside `[0, length)` return [`Error::ShortRead`] with `got: 0`;
212/// writes outside it return [`Error::OutOfBounds`]. The two directions
213/// differ on purpose — see the module docs.
214pub struct OwnedRwSlice {
215    parent: Arc<dyn BlockDevice>,
216    geom: SliceGeometry,
217}
218
219impl OwnedRwSlice {
220    pub fn new(parent: Arc<dyn BlockDevice>, start: u64, length: u64) -> Self {
221        Self {
222            parent,
223            geom: SliceGeometry::new(start, length),
224        }
225    }
226
227    /// Byte offset of this slice on the parent device.
228    pub fn start(&self) -> u64 {
229        self.geom.start
230    }
231
232    /// Length of this slice in bytes (== `size_bytes()`).
233    pub fn length(&self) -> u64 {
234        self.geom.length
235    }
236}
237
238impl BlockRead for OwnedRwSlice {
239    fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
240        let at = self.geom.rebase_read(offset, buf.len())?;
241        self.parent.read_at(at, buf)
242    }
243
244    fn size_bytes(&self) -> u64 {
245        self.geom.length
246    }
247}
248
249impl BlockDevice for OwnedRwSlice {
250    /// Range first, writability second: a write that is both out of range
251    /// and aimed at a read-only parent reports [`Error::OutOfBounds`],
252    /// not [`Error::ReadOnly`]. The range is a property of this slice and
253    /// is knowable without asking the parent anything, so it is the more
254    /// specific of the two answers.
255    fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
256        let at = self.geom.rebase_write(offset, buf.len())?;
257        if !self.parent.is_writable() {
258            return Err(Error::ReadOnly);
259        }
260        self.parent.write_at(at, buf)
261    }
262
263    fn flush(&self) -> Result<()> {
264        self.parent.flush()
265    }
266
267    fn is_writable(&self) -> bool {
268        self.parent.is_writable()
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use std::sync::Mutex;
276
277    struct Bytes(Mutex<Vec<u8>>);
278    impl BlockRead for Bytes {
279        fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
280            let b = self.0.lock().unwrap();
281            let start = offset as usize;
282            let end = start + buf.len();
283            if end > b.len() {
284                return Err(Error::ShortRead {
285                    offset,
286                    want: buf.len(),
287                    got: b.len().saturating_sub(start),
288                });
289            }
290            buf.copy_from_slice(&b[start..end]);
291            Ok(())
292        }
293        fn size_bytes(&self) -> u64 {
294            self.0.lock().unwrap().len() as u64
295        }
296    }
297
298    #[test]
299    fn slice_reader_rebases_offsets() {
300        let mut v = vec![0u8; 4096];
301        v[2000..2004].copy_from_slice(&[0xAB, 0xCD, 0xEF, 0x01]);
302        let dev = Bytes(Mutex::new(v));
303
304        let slice = SliceReader::new(&dev, 2000, 4);
305        assert_eq!(slice.size_bytes(), 4);
306        assert_eq!(slice.start(), 2000);
307        assert_eq!(slice.length(), 4);
308
309        let mut buf = [0u8; 4];
310        slice.read_at(0, &mut buf).unwrap();
311        assert_eq!(buf, [0xAB, 0xCD, 0xEF, 0x01]);
312    }
313
314    #[test]
315    fn slice_reader_rejects_out_of_bounds() {
316        let dev = Bytes(Mutex::new(vec![0u8; 4096]));
317        let slice = SliceReader::new(&dev, 0, 16);
318        let mut buf = [0u8; 8];
319        match slice.read_at(12, &mut buf) {
320            Err(Error::ShortRead { .. }) => {}
321            other => panic!("expected ShortRead, got {other:?}"),
322        }
323    }
324
325    #[test]
326    fn owned_slice_works_through_arc() {
327        let mut v = vec![0u8; 4096];
328        v[100..104].copy_from_slice(&[0x11, 0x22, 0x33, 0x44]);
329        let dev: Arc<dyn BlockRead> = Arc::new(Bytes(Mutex::new(v)));
330
331        let slice = OwnedSlice::new(dev, 100, 4);
332        assert_eq!(slice.size_bytes(), 4);
333        let mut buf = [0u8; 4];
334        slice.read_at(0, &mut buf).unwrap();
335        assert_eq!(buf, [0x11, 0x22, 0x33, 0x44]);
336    }
337
338    #[test]
339    fn slices_reject_writes_via_blockdevice_default() {
340        let dev = Bytes(Mutex::new(vec![0u8; 16]));
341        let slice = SliceReader::new(&dev, 0, 8);
342        let err = BlockDevice::write_at(&slice, 0, &[1u8; 4]).unwrap_err();
343        assert!(matches!(err, Error::ReadOnly));
344    }
345
346    #[test]
347    fn owned_slice_accessors_report_geometry() {
348        let dev: Arc<dyn BlockRead> = Arc::new(Bytes(Mutex::new(vec![0u8; 4096])));
349        let slice = OwnedSlice::new(dev, 512, 256);
350        assert_eq!(slice.start(), 512);
351        assert_eq!(slice.length(), 256);
352        assert_eq!(slice.size_bytes(), 256);
353    }
354
355    /// Writable in-memory device for exercising `OwnedRwSlice`.
356    struct RwBytes(Mutex<Vec<u8>>);
357    impl BlockRead for RwBytes {
358        fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
359            let b = self.0.lock().unwrap();
360            let start = offset as usize;
361            let end = start + buf.len();
362            if end > b.len() {
363                return Err(Error::ShortRead {
364                    offset,
365                    want: buf.len(),
366                    got: b.len().saturating_sub(start),
367                });
368            }
369            buf.copy_from_slice(&b[start..end]);
370            Ok(())
371        }
372        fn size_bytes(&self) -> u64 {
373            self.0.lock().unwrap().len() as u64
374        }
375    }
376    impl BlockDevice for RwBytes {
377        fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
378            let mut b = self.0.lock().unwrap();
379            let s = offset as usize;
380            b[s..s + buf.len()].copy_from_slice(buf);
381            Ok(())
382        }
383        fn is_writable(&self) -> bool {
384            true
385        }
386    }
387
388    #[test]
389    fn owned_rw_slice_accessors_report_geometry() {
390        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
391        let slice = OwnedRwSlice::new(dev, 16, 32);
392        assert_eq!(slice.start(), 16);
393        assert_eq!(slice.length(), 32);
394        assert_eq!(slice.size_bytes(), 32);
395        assert!(slice.is_writable());
396    }
397
398    #[test]
399    fn owned_rw_slice_rebases_reads_and_writes() {
400        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
401        let slice = OwnedRwSlice::new(dev.clone(), 16, 32);
402
403        // Write through the slice lands at parent offset 16.
404        slice.write_at(0, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
405        let mut buf = [0u8; 4];
406        slice.read_at(0, &mut buf).unwrap();
407        assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
408
409        // Confirm rebasing against the parent directly.
410        let mut pbuf = [0u8; 4];
411        dev.read_at(16, &mut pbuf).unwrap();
412        assert_eq!(pbuf, [0xDE, 0xAD, 0xBE, 0xEF]);
413    }
414
415    #[test]
416    fn owned_rw_slice_rejects_out_of_bounds_write() {
417        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
418        let slice = OwnedRwSlice::new(dev, 0, 8);
419        match slice.write_at(6, &[0u8; 4]) {
420            Err(Error::OutOfBounds { .. }) => {}
421            other => panic!("expected OutOfBounds, got {other:?}"),
422        }
423    }
424
425    /// The bounds rule is direction-dependent by design: one slice, one
426    /// out-of-range span, two different errors. Pinned here so the
427    /// asymmetry cannot be "tidied up" into consistency without someone
428    /// deciding to — the reasoning is in the module docs.
429    #[test]
430    fn same_out_of_range_span_is_short_read_for_a_read_and_out_of_bounds_for_a_write() {
431        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
432        let slice = OwnedRwSlice::new(dev, 16, 8);
433
434        let mut buf = [0u8; 4];
435        match slice.read_at(6, &mut buf) {
436            Err(Error::ShortRead { offset, want, got }) => {
437                assert_eq!((offset, want, got), (6, 4, 0));
438            }
439            other => panic!("expected ShortRead, got {other:?}"),
440        }
441
442        match slice.write_at(6, &[0u8; 4]) {
443            Err(Error::OutOfBounds { offset, len, size }) => {
444                assert_eq!((offset, len, size), (6, 4, 8));
445            }
446            other => panic!("expected OutOfBounds, got {other:?}"),
447        }
448    }
449
450    #[test]
451    fn owned_rw_slice_flush_delegates_to_parent() {
452        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 8])));
453        let slice = OwnedRwSlice::new(dev, 0, 8);
454        // Default `flush` on RwBytes is a no-op success; the slice forwards it.
455        slice.flush().unwrap();
456    }
457}