Skip to main content

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>;