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//! # A slice cannot report more device than its parent holds
54//!
55//! All three constructors ask the parent its size and CLAMP `length` to
56//! what is actually there — see [`window_on_parent`] for the rule and
57//! why it clamps rather than refuses. So `size_bytes()` is the truth
58//! about how much is readable, not a number the caller asserted.
59//!
60//! That is load-bearing rather than tidy. `size_bytes()` is "used for
61//! bounds checks" ([`BlockRead::size_bytes`]), and a driver mounted on a
62//! slice sizes its own structures from it. A slice that claimed a
63//! megabyte over a hundred-byte parent kept both promises of the section
64//! above and broke them in the same breath: a read inside the *declared*
65//! length but past the *real* end was forwarded, and the parent — being
66//! a real device — answered with a `ShortRead` carrying its own absolute
67//! offset and a non-zero `got`, having already copied the readable prefix
68//! into the caller's buffer. Substitutable for a real device of size
69//! `length` is exactly what that is not.
70//!
71//! A slice whose `start` is at or past the parent's end has nothing
72//! behind it at all. The constructors are infallible and give it
73//! `length = 0`, which behaves as a zero-byte device does: every read is
74//! `ShortRead { got: 0 }` and every write is `OutOfBounds { size: 0 }`.
75//! The C ABI, which can afford to be fallible, refuses it instead —
76//! `fs_core_device_slice_ro`/`_rw` return NULL with a message.
77//!
78//! [`FileDevice`]: crate::FileDevice
79
80use crate::block::{BlockDevice, BlockRead};
81use crate::error::{Error, Result};
82use std::sync::Arc;
83
84/// How much of the window `[start, start + length)` is actually on a
85/// parent of `parent_size` bytes, or `None` when the window begins at or
86/// past the parent's end and there is nothing on it to slice.
87///
88/// A slice's geometry comes from a partition table, and a partition
89/// table comes off the disk, so a window that claims to start or end
90/// past the device is an ordinary thing to be handed -- and not only
91/// from a hostile image. A `dd` of the first N gigabytes of a disk, or a
92/// table left stale after the volume was shrunk, both produce a last
93/// partition that runs off the end.
94///
95/// So the length is CLAMPED rather than the slice refused. Refusing it
96/// takes away the one thing someone with a truncated image wants, which
97/// is to read what is still there. What must not happen is the slice
98/// reporting more device than exists, because the driver stacked on it
99/// sizes its own structures from that answer.
100///
101/// Clamping also closes the arithmetic hole in the slices' shared
102/// rebasing step as a side effect, and closes it at the root.
103/// `start + offset + len`
104/// can only leave a `u64` if `start + length` does, and `length` is now
105/// at most `parent_size - start`.
106pub fn window_on_parent(parent_size: u64, start: u64, length: u64) -> Option<u64> {
107 if start >= parent_size {
108 return None;
109 }
110 Some(length.min(parent_size - start))
111}
112
113/// Where a slice sits on its parent, and the one bounds rule the three
114/// slice types share.
115///
116/// The public slice types differ only in how they hold the parent and
117/// whether writes propagate. The geometry, the range check and the choice
118/// of error are identical across all of them, so they live here — one
119/// definition to read, one place to change.
120#[derive(Clone, Copy)]
121struct SliceGeometry {
122 start: u64,
123 length: u64,
124}
125
126impl SliceGeometry {
127 /// `length` is CLAMPED to what `parent_size` can back — see
128 /// [`window_on_parent`]. A window beginning at or past the parent's
129 /// end becomes a zero-length slice, which is what it is.
130 ///
131 /// Taking the parent's size here rather than on each read is
132 /// deliberate: [`BlockRead::size_bytes`] must not change for the
133 /// life of a device, so one call at construction is the whole
134 /// answer, and `length` is then a fact instead of a claim.
135 fn new(parent_size: u64, start: u64, length: u64) -> Self {
136 Self {
137 start,
138 length: window_on_parent(parent_size, start, length).unwrap_or(0),
139 }
140 }
141
142 /// Parent offset corresponding to `offset`, or `None` when
143 /// `[offset, offset + len)` is not wholly inside `[0, length)`.
144 ///
145 /// This asks the parent nothing — the parent was asked once, in
146 /// [`SliceGeometry::new`], and `length` is its answer. The comment
147 /// here used to claim a second check "when the rebased offset would
148 /// not fit on the parent at all", which no code performed and no
149 /// parent was in scope to perform: it was a `u64` overflow guard
150 /// being described as a bounds check against the device.
151 ///
152 /// Both additions stay checked, `start + offset` included. That one
153 /// used to be deliberate, on the argument that a slice built with a
154 /// nonsense `start` would "overflow here rather than quietly
155 /// reading some other part of the parent" -- which holds only while
156 /// `overflow-checks` is on, and it is off in the release profile
157 /// these crates ship. In release the addition wrapped, and the wrap
158 /// did precisely the thing the argument said it avoided: a slice
159 /// starting at 2^63 and 5000 bytes long returned `Ok` and the
160 /// parent's bytes from offset 5000.
161 ///
162 /// Both are now unreachable through the public API, because the
163 /// clamp makes `start + length <= parent_size` and every accepted
164 /// `offset + len` is at most `length`. They are kept as the last
165 /// line of defence for a parent that violates the `size_bytes`
166 /// stability contract and shrinks under a live slice; the guard that
167 /// is actually load-bearing, and tested, is the clamp.
168 fn rebase(&self, offset: u64, len: u64) -> Option<u64> {
169 let end = offset.checked_add(len)?;
170 if end > self.length {
171 return None;
172 }
173 self.start.checked_add(offset)
174 }
175
176 /// Bounds-check a read and rebase it onto the parent.
177 ///
178 /// Out of range is [`Error::ShortRead`] with `got: 0` — the same
179 /// answer a real device of size `length` gives for a read beginning
180 /// at or past its end. See the module docs for why.
181 fn rebase_read(&self, offset: u64, len: usize) -> Result<u64> {
182 self.rebase(offset, len as u64).ok_or(Error::ShortRead {
183 offset,
184 want: len,
185 got: 0,
186 })
187 }
188
189 /// Bounds-check a write and rebase it onto the parent.
190 ///
191 /// Out of range is [`Error::OutOfBounds`]: nothing was written, and
192 /// the caller is handed the slice's size so it can clamp and retry.
193 fn rebase_write(&self, offset: u64, len: usize) -> Result<u64> {
194 self.rebase(offset, len as u64).ok_or(Error::OutOfBounds {
195 offset,
196 len: len as u64,
197 size: self.length,
198 })
199 }
200}
201
202/// Borrowed slice of a parent `BlockRead`.
203///
204/// `read_at(0, …)` reads `start` of the parent. Reads outside
205/// `[0, length)` return [`Error::ShortRead`] with `got: 0`.
206pub struct SliceReader<'a> {
207 parent: &'a (dyn BlockRead + 'a),
208 geom: SliceGeometry,
209}
210
211impl<'a> SliceReader<'a> {
212 /// `length` is clamped to what the parent can back, so
213 /// `size_bytes()` never exceeds it. See [`window_on_parent`].
214 pub fn new(parent: &'a (dyn BlockRead + 'a), start: u64, length: u64) -> Self {
215 let geom = SliceGeometry::new(parent.size_bytes(), start, length);
216 Self { parent, geom }
217 }
218
219 /// Byte offset of this slice on the parent device.
220 pub fn start(&self) -> u64 {
221 self.geom.start
222 }
223
224 /// Length of this slice in bytes (== `size_bytes()`).
225 pub fn length(&self) -> u64 {
226 self.geom.length
227 }
228}
229
230impl<'a> BlockRead for SliceReader<'a> {
231 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
232 let at = self.geom.rebase_read(offset, buf.len())?;
233 self.parent.read_at(at, buf)
234 }
235
236 fn size_bytes(&self) -> u64 {
237 self.geom.length
238 }
239}
240
241/// Slices are read-only by default — even where the parent is writable,
242/// slicing is almost always paired with a read-only inspection or
243/// dispatch workflow.
244impl<'a> BlockDevice for SliceReader<'a> {}
245
246/// Owned slice over an `Arc<dyn BlockRead>`. Use when the parent's
247/// lifetime can't be expressed in a borrow — e.g. when the slice is
248/// handed across an FFI boundary or stored in a long-lived struct.
249///
250/// Reads outside `[0, length)` return [`Error::ShortRead`] with `got: 0`.
251pub struct OwnedSlice {
252 parent: Arc<dyn BlockRead>,
253 geom: SliceGeometry,
254}
255
256impl OwnedSlice {
257 /// `length` is clamped to what the parent can back, so
258 /// `size_bytes()` never exceeds it. See [`window_on_parent`].
259 pub fn new(parent: Arc<dyn BlockRead>, start: u64, length: u64) -> Self {
260 let geom = SliceGeometry::new(parent.size_bytes(), start, length);
261 Self { parent, geom }
262 }
263
264 /// Byte offset of this slice on the parent device.
265 pub fn start(&self) -> u64 {
266 self.geom.start
267 }
268
269 /// Length of this slice in bytes (== `size_bytes()`).
270 pub fn length(&self) -> u64 {
271 self.geom.length
272 }
273}
274
275impl BlockRead for OwnedSlice {
276 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
277 let at = self.geom.rebase_read(offset, buf.len())?;
278 self.parent.read_at(at, buf)
279 }
280
281 fn size_bytes(&self) -> u64 {
282 self.geom.length
283 }
284}
285
286/// Same rationale as `SliceReader`: read-only by default.
287impl BlockDevice for OwnedSlice {}
288
289/// Owned, read-WRITE slice over an `Arc<dyn BlockDevice>`. Use when the
290/// parent is writable and the slice should propagate writes (e.g. an
291/// individual partition handed to a filesystem driver).
292///
293/// Reads outside `[0, length)` return [`Error::ShortRead`] with `got: 0`;
294/// writes outside it return [`Error::OutOfBounds`]. The two directions
295/// differ on purpose — see the module docs.
296pub struct OwnedRwSlice {
297 parent: Arc<dyn BlockDevice>,
298 geom: SliceGeometry,
299}
300
301impl OwnedRwSlice {
302 /// `length` is clamped to what the parent can back, so
303 /// `size_bytes()` never exceeds it and a write past the parent's end
304 /// is [`Error::OutOfBounds`] from this slice rather than whatever
305 /// the parent makes of an address beyond itself. See
306 /// [`window_on_parent`].
307 pub fn new(parent: Arc<dyn BlockDevice>, start: u64, length: u64) -> Self {
308 let geom = SliceGeometry::new(parent.size_bytes(), start, length);
309 Self { parent, geom }
310 }
311
312 /// Byte offset of this slice on the parent device.
313 pub fn start(&self) -> u64 {
314 self.geom.start
315 }
316
317 /// Length of this slice in bytes (== `size_bytes()`).
318 pub fn length(&self) -> u64 {
319 self.geom.length
320 }
321}
322
323impl BlockRead for OwnedRwSlice {
324 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
325 let at = self.geom.rebase_read(offset, buf.len())?;
326 self.parent.read_at(at, buf)
327 }
328
329 fn size_bytes(&self) -> u64 {
330 self.geom.length
331 }
332}
333
334impl BlockDevice for OwnedRwSlice {
335 /// Range first, writability second: a write that is both out of range
336 /// and aimed at a read-only parent reports [`Error::OutOfBounds`],
337 /// not [`Error::ReadOnly`]. The range is a property of this slice and
338 /// is knowable without asking the parent anything, so it is the more
339 /// specific of the two answers.
340 fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
341 let at = self.geom.rebase_write(offset, buf.len())?;
342 if !self.parent.is_writable() {
343 return Err(Error::ReadOnly);
344 }
345 self.parent.write_at(at, buf)
346 }
347
348 fn flush(&self) -> Result<()> {
349 self.parent.flush()
350 }
351
352 fn is_writable(&self) -> bool {
353 self.parent.is_writable()
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use crate::test_device::{Bytes, RwBytes};
361 use std::sync::Mutex;
362
363 #[test]
364 fn slice_reader_rebases_offsets() {
365 let mut v = vec![0u8; 4096];
366 v[2000..2004].copy_from_slice(&[0xAB, 0xCD, 0xEF, 0x01]);
367 let dev = Bytes(Mutex::new(v));
368
369 let slice = SliceReader::new(&dev, 2000, 4);
370 assert_eq!(slice.size_bytes(), 4);
371 assert_eq!(slice.start(), 2000);
372 assert_eq!(slice.length(), 4);
373
374 let mut buf = [0u8; 4];
375 slice.read_at(0, &mut buf).unwrap();
376 assert_eq!(buf, [0xAB, 0xCD, 0xEF, 0x01]);
377 }
378
379 /// A slice's geometry comes from a partition table, and a partition
380 /// table comes off the disk. A start and a length that add up past
381 /// 2^64 are an ordinary thing to be handed.
382 ///
383 /// In a release build the rebasing addition wrapped, so a read at an
384 /// offset inside the slice's declared length landed somewhere else
385 /// on the parent entirely -- and came back `Ok`, with those bytes,
386 /// as though they were the slice's own.
387 ///
388 /// The `checked_add` in `rebase` is no longer what stops this: the
389 /// constructor's clamp gets there first, and a start of 2^63 over a
390 /// 64 KiB parent now leaves nothing to read at all. Both assertions
391 /// are kept -- the size, which is the guard now in force, and the
392 /// bytes, which are what went wrong.
393 #[test]
394 fn a_slice_whose_start_plus_offset_leaves_the_parent_reads_nothing() {
395 let mut v = vec![0u8; 64 * 1024];
396 v[5000..5008].copy_from_slice(b"SECRET!!");
397 let dev: Arc<dyn BlockRead> = Arc::new(Bytes(Mutex::new(v)));
398
399 // A GPT entry of starting_lba = 2^54 and ending_lba = 2^55 + 99
400 // produces exactly this.
401 let slice = OwnedSlice::new(dev, 1 << 63, (1 << 63) + 51200);
402 assert_eq!(
403 slice.size_bytes(),
404 0,
405 "the window begins past the parent, so none of it is there"
406 );
407 let mut buf = [0u8; 8];
408 let inside_the_declared_length = (1u64 << 63) + 5000;
409
410 let outcome = slice.read_at(inside_the_declared_length, &mut buf);
411 assert!(
412 outcome.is_err(),
413 "the read succeeded and returned {:?}, which is the parent's \
414 bytes from offset 5000",
415 std::str::from_utf8(&buf)
416 );
417 assert_ne!(&buf, b"SECRET!!");
418 }
419
420 #[test]
421 fn slice_reader_rejects_out_of_bounds() {
422 let dev = Bytes(Mutex::new(vec![0u8; 4096]));
423 let slice = SliceReader::new(&dev, 0, 16);
424 let mut buf = [0u8; 8];
425 match slice.read_at(12, &mut buf) {
426 Err(Error::ShortRead { .. }) => {}
427 other => panic!("expected ShortRead, got {other:?}"),
428 }
429 }
430
431 #[test]
432 fn owned_slice_works_through_arc() {
433 let mut v = vec![0u8; 4096];
434 v[100..104].copy_from_slice(&[0x11, 0x22, 0x33, 0x44]);
435 let dev: Arc<dyn BlockRead> = Arc::new(Bytes(Mutex::new(v)));
436
437 let slice = OwnedSlice::new(dev, 100, 4);
438 assert_eq!(slice.size_bytes(), 4);
439 let mut buf = [0u8; 4];
440 slice.read_at(0, &mut buf).unwrap();
441 assert_eq!(buf, [0x11, 0x22, 0x33, 0x44]);
442 }
443
444 #[test]
445 fn slices_reject_writes_via_blockdevice_default() {
446 let dev = Bytes(Mutex::new(vec![0u8; 16]));
447 let slice = SliceReader::new(&dev, 0, 8);
448 let err = BlockDevice::write_at(&slice, 0, &[1u8; 4]).unwrap_err();
449 assert!(matches!(err, Error::ReadOnly));
450 }
451
452 #[test]
453 fn owned_slice_accessors_report_geometry() {
454 let dev: Arc<dyn BlockRead> = Arc::new(Bytes(Mutex::new(vec![0u8; 4096])));
455 let slice = OwnedSlice::new(dev, 512, 256);
456 assert_eq!(slice.start(), 512);
457 assert_eq!(slice.length(), 256);
458 assert_eq!(slice.size_bytes(), 256);
459 }
460
461 #[test]
462 fn owned_rw_slice_accessors_report_geometry() {
463 let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
464 let slice = OwnedRwSlice::new(dev, 16, 32);
465 assert_eq!(slice.start(), 16);
466 assert_eq!(slice.length(), 32);
467 assert_eq!(slice.size_bytes(), 32);
468 assert!(slice.is_writable());
469 }
470
471 #[test]
472 fn owned_rw_slice_rebases_reads_and_writes() {
473 let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
474 let slice = OwnedRwSlice::new(dev.clone(), 16, 32);
475
476 // Write through the slice lands at parent offset 16.
477 slice.write_at(0, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
478 let mut buf = [0u8; 4];
479 slice.read_at(0, &mut buf).unwrap();
480 assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
481
482 // Confirm rebasing against the parent directly.
483 let mut pbuf = [0u8; 4];
484 dev.read_at(16, &mut pbuf).unwrap();
485 assert_eq!(pbuf, [0xDE, 0xAD, 0xBE, 0xEF]);
486 }
487
488 #[test]
489 fn owned_rw_slice_rejects_out_of_bounds_write() {
490 let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
491 let slice = OwnedRwSlice::new(dev, 0, 8);
492 match slice.write_at(6, &[0u8; 4]) {
493 Err(Error::OutOfBounds { .. }) => {}
494 other => panic!("expected OutOfBounds, got {other:?}"),
495 }
496 }
497
498 /// The bounds rule is direction-dependent by design: one slice, one
499 /// out-of-range span, two different errors. Pinned here so the
500 /// asymmetry cannot be "tidied up" into consistency without someone
501 /// deciding to — the reasoning is in the module docs.
502 #[test]
503 fn same_out_of_range_span_is_short_read_for_a_read_and_out_of_bounds_for_a_write() {
504 let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
505 let slice = OwnedRwSlice::new(dev, 16, 8);
506
507 let mut buf = [0u8; 4];
508 match slice.read_at(6, &mut buf) {
509 Err(Error::ShortRead { offset, want, got }) => {
510 assert_eq!((offset, want, got), (6, 4, 0));
511 }
512 other => panic!("expected ShortRead, got {other:?}"),
513 }
514
515 match slice.write_at(6, &[0u8; 4]) {
516 Err(Error::OutOfBounds { offset, len, size }) => {
517 assert_eq!((offset, len, size), (6, 4, 8));
518 }
519 other => panic!("expected OutOfBounds, got {other:?}"),
520 }
521 }
522
523 /// The rule itself, at its edges. Clamp where the window merely runs
524 /// off the end; `None` only where it begins at or past the end and
525 /// there is nothing to slice.
526 #[test]
527 fn window_on_parent_clamps_the_length_and_refuses_only_a_start_past_the_end() {
528 // Wholly inside: untouched.
529 assert_eq!(window_on_parent(1024, 0, 1024), Some(1024));
530 assert_eq!(window_on_parent(1024, 512, 512), Some(512));
531 assert_eq!(window_on_parent(1024, 1023, 1), Some(1));
532
533 // Running off the end: as much of it as is there. A `dd` of the
534 // first part of a disk, or a table left stale after a shrink.
535 assert_eq!(window_on_parent(1024, 512, 513), Some(512));
536 assert_eq!(window_on_parent(1024, 0, u64::MAX), Some(1024));
537
538 // Beginning at or past the end: nothing on the parent to slice.
539 assert_eq!(window_on_parent(1024, 1024, 1), None);
540 assert_eq!(window_on_parent(1024, 4096, 1), None);
541 assert_eq!(window_on_parent(0, 0, 8), None);
542
543 // The pair a GPT entry of starting_lba 2^54 and ending_lba
544 // 2^55 + 99 produces: the sum leaves a u64 entirely, and the
545 // start alone is already past the parent.
546 assert_eq!(
547 window_on_parent(64 * 1024, 1 << 63, (1 << 63) + 51200),
548 None
549 );
550
551 // The arithmetic case from the top of the address space. The
552 // clamp is what keeps `start + offset + len` inside a u64: 4
553 // bytes rebase to exactly `u64::MAX`, and the requested 8 would
554 // not have.
555 assert_eq!(window_on_parent(u64::MAX, u64::MAX - 4, 8), Some(4));
556 }
557
558 /// `size_bytes()` is what bounds checks are done against, so a
559 /// borrowed slice must not report a window its parent cannot back.
560 ///
561 /// The read assertion is on the ShortRead's FIELDS, not on
562 /// `is_err()`. An unclamped slice rebases 45 to 95 and the parent
563 /// refuses that too -- the error arrives either way. What tells the
564 /// two apart is whose bounds were consulted: the slice's own offset
565 /// and `got: 0`, or the parent's absolute 95 and the five bytes it
566 /// could have delivered.
567 #[test]
568 fn a_borrowed_slice_cannot_claim_more_than_its_parent_holds() {
569 let dev = Bytes::new(vec![0xEE; 100]);
570 let slice = SliceReader::new(&dev, 50, 100);
571
572 assert_eq!(slice.size_bytes(), 50);
573 assert_eq!(slice.length(), 50);
574 assert_eq!(
575 slice.start(),
576 50,
577 "the start is not clamped, only the length"
578 );
579
580 let mut buf = [0u8; 8];
581 match slice.read_at(45, &mut buf) {
582 Err(Error::ShortRead { offset, want, got }) => {
583 assert_eq!((offset, want, got), (45, 8, 0));
584 }
585 other => panic!("expected the slice's own ShortRead, got {other:?}"),
586 }
587 assert_eq!(buf, [0u8; 8], "a refused read leaves the buffer alone");
588 }
589
590 /// The same for the `Arc` variant, which is the one the C ABI and
591 /// the partition walkers reach.
592 #[test]
593 fn an_owned_slice_cannot_claim_more_than_its_parent_holds() {
594 let mut v = vec![0u8; 100];
595 v[50..58].copy_from_slice(b"LASTHALF");
596 let dev: Arc<dyn BlockRead> = Arc::new(Bytes::new(v));
597 let slice = OwnedSlice::new(dev, 50, 100);
598
599 assert_eq!(slice.size_bytes(), 50);
600 assert_eq!(slice.length(), 50);
601
602 // What is there still reads.
603 let mut buf = [0u8; 8];
604 slice.read_at(0, &mut buf).unwrap();
605 assert_eq!(&buf, b"LASTHALF");
606
607 match slice.read_at(45, &mut buf) {
608 Err(Error::ShortRead { offset, want, got }) => {
609 assert_eq!((offset, want, got), (45, 8, 0));
610 }
611 other => panic!("expected the slice's own ShortRead, got {other:?}"),
612 }
613 }
614
615 /// The write direction has its own currency, and an over-long slice
616 /// lost it: the write was inside the declared length, so it was
617 /// forwarded, and the caller got whatever the parent makes of an
618 /// address beyond itself -- a `ShortRead` from a WRITE, in the case
619 /// of the in-memory double, and a silently extended file in the case
620 /// of `FileDevice`, which seeks and writes without a bounds check.
621 #[test]
622 fn an_rw_slice_refuses_a_write_past_its_parents_end_as_out_of_bounds() {
623 let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes::new(vec![0u8; 64]));
624 let slice = OwnedRwSlice::new(dev.clone(), 32, 4096);
625
626 assert_eq!(slice.size_bytes(), 32);
627
628 // Inside the clamped window still writes through.
629 slice.write_at(0, &[0xAB; 4]).unwrap();
630 let mut pbuf = [0u8; 4];
631 dev.read_at(32, &mut pbuf).unwrap();
632 assert_eq!(pbuf, [0xAB; 4]);
633
634 match slice.write_at(32, &[0xCD; 8]) {
635 Err(Error::OutOfBounds { offset, len, size }) => {
636 assert_eq!((offset, len, size), (32, 8, 32));
637 }
638 other => panic!("expected the slice's own OutOfBounds, got {other:?}"),
639 }
640 assert_eq!(dev.size_bytes(), 64, "the parent did not grow");
641 }
642
643 /// A window that begins at or past the parent's end is a zero-byte
644 /// device, not a window onto somewhere else.
645 #[test]
646 fn a_slice_starting_past_its_parents_end_is_empty() {
647 let mut v = vec![0u8; 100];
648 v[0..8].copy_from_slice(b"NOTYOURS");
649 let dev: Arc<dyn BlockRead> = Arc::new(Bytes::new(v));
650 let slice = OwnedSlice::new(dev, 200, 64);
651
652 assert_eq!(slice.size_bytes(), 0);
653
654 let mut buf = [0u8; 8];
655 match slice.read_at(0, &mut buf) {
656 Err(Error::ShortRead { offset, want, got }) => {
657 assert_eq!((offset, want, got), (0, 8, 0));
658 }
659 other => panic!("expected ShortRead, got {other:?}"),
660 }
661 assert_eq!(buf, [0u8; 8]);
662 }
663
664 #[test]
665 fn owned_rw_slice_flush_delegates_to_parent() {
666 let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 8])));
667 let slice = OwnedRwSlice::new(dev, 0, 8);
668 // Default `flush` on RwBytes is a no-op success; the slice forwards it.
669 slice.flush().unwrap();
670 }
671}