fs_core/error.rs
1//! Unified error type. Each driver still keeps its own rich error type for
2//! internal use; conversions to/from this one happen at the trait boundary.
3
4use std::fmt;
5use std::io;
6
7#[derive(Debug)]
8pub enum Error {
9 /// Underlying I/O failure (open, seek, read, write).
10 Io(io::Error),
11 /// A read that could not be satisfied in full: the source ran out of
12 /// data before `want` bytes had been transferred.
13 ///
14 /// `got` counts the bytes actually placed in the caller's buffer, not
15 /// the bytes that were available. [`FileDevice`] copies what it can
16 /// and reports that count, so a read straddling EOF comes back with
17 /// the readable prefix in `buf` and `got` equal to its length. The
18 /// slice adapters in [`crate::slice`] refuse an out-of-range read
19 /// before touching the parent, so they leave `buf` untouched and
20 /// always report `got: 0` — including for a read that begins inside
21 /// the slice and runs off its end, where a [`FileDevice`] of the same
22 /// size would have reported a non-zero prefix. So **`got: 0` always
23 /// means nothing was transferred, and does not on its own tell you
24 /// whether anything was available**: from a [`FileDevice`] at EOF it
25 /// happens to mean both, from a slice it means only the former.
26 ///
27 /// It is not the only error an over-read can produce, because most of
28 /// this crate's devices do not own the bytes they serve:
29 ///
30 /// - [`crate::CachingDevice`], [`crate::ReadOnlyDevice`] and the
31 /// slice adapters forward an in-range read to their parent and
32 /// return the parent's error unchanged. Over a parent that reports
33 /// over-reads as [`Error::OutOfBounds`] — the `img-*` container
34 /// readers do — that is what the wrapper reports too.
35 /// - [`crate::CallbackDevice`] surfaces a failing host callback as
36 /// [`Error::Io`]. The callback ABI is an errno-space code carrying
37 /// no byte count, so there is nothing to put in `got`.
38 ///
39 /// [`FileDevice`]: crate::FileDevice
40 ShortRead {
41 offset: u64,
42 want: usize,
43 got: usize,
44 },
45 /// `write_at` invoked on a device opened read-only.
46 ReadOnly,
47 /// A request refused before any transfer because its range is not
48 /// wholly inside the device's declared size. Nothing was read or
49 /// written; `size` is the device size, so the caller can clamp.
50 ///
51 /// **This crate constructs it in exactly two places, both writes:**
52 /// [`crate::OwnedRwSlice`]'s `write_at`, for a write outside the
53 /// slice, and [`FileDevice`]'s `write_at`, for a write outside the
54 /// size that device took at construction. No read path here builds
55 /// it, so matching it to catch an over-read of a [`FileDevice`], or
56 /// a read past a slice's own end, is an arm that will never be
57 /// taken — those report [`Error::ShortRead`] with `got: 0`.
58 ///
59 /// The count is pinned by `tests/outofbounds_constructors.rs`
60 /// against the source, because this paragraph said "exactly one
61 /// place" for as long as it took someone to add the second.
62 ///
63 /// **It still reaches reads, from elsewhere.** A container that knows
64 /// its virtual size before touching the backing store rejects an
65 /// over-read up front rather than discovering EOF, and the
66 /// `img-qcow2`, `img-vhd`, `img-vhdx` and `img-vmdk` readers all
67 /// return `OutOfBounds` from `BlockRead::read_at` on that path. This
68 /// crate's wrappers — [`crate::CachingDevice`],
69 /// [`crate::ReadOnlyDevice`], the slice adapters — forward it
70 /// unchanged from such a parent.
71 ///
72 /// So code reading through a `dyn BlockRead` of unknown provenance
73 /// has to handle this as well as [`Error::ShortRead`] — and cannot
74 /// treat the pair as exhaustive, because a
75 /// [`crate::CallbackDevice`] reports its host's refusal as
76 /// [`Error::Io`] however the host arrived at it. Only code that knows
77 /// its device bottoms out in a [`FileDevice`] can rely on `ShortRead`
78 /// alone.
79 ///
80 /// [`FileDevice`]: crate::FileDevice
81 OutOfBounds { offset: u64, len: u64, size: u64 },
82 /// Driver-specific error lifted to the trait boundary. Each driver's
83 /// internal error type implements `Into<Error>` via this variant.
84 Custom(String),
85}
86
87impl fmt::Display for Error {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 match self {
90 Error::Io(e) => write!(f, "io: {e}"),
91 Error::ShortRead { offset, want, got } => {
92 write!(f, "short read at {offset}: wanted {want} got {got}")
93 }
94 Error::ReadOnly => write!(f, "device is read-only"),
95 Error::OutOfBounds { offset, len, size } => {
96 write!(f, "{offset}+{len} past device size {size}")
97 }
98 Error::Custom(s) => f.write_str(s),
99 }
100 }
101}
102
103impl std::error::Error for Error {
104 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
105 match self {
106 Error::Io(e) => Some(e),
107 _ => None,
108 }
109 }
110}
111
112impl From<io::Error> for Error {
113 fn from(e: io::Error) -> Self {
114 Error::Io(e)
115 }
116}
117
118pub type Result<T> = std::result::Result<T, Error>;