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 one place:**
52    /// [`crate::OwnedRwSlice`]'s `write_at`, for a write outside the
53    /// slice. No read path here builds it, so matching it to catch an
54    /// over-read of a [`FileDevice`], or a read past a slice's own end,
55    /// is an arm that will never be taken — those report
56    /// [`Error::ShortRead`] with `got: 0`.
57    ///
58    /// **It still reaches reads, from elsewhere.** A container that knows
59    /// its virtual size before touching the backing store rejects an
60    /// over-read up front rather than discovering EOF, and the
61    /// `img-qcow2`, `img-vhd`, `img-vhdx` and `img-vmdk` readers all
62    /// return `OutOfBounds` from `BlockRead::read_at` on that path. This
63    /// crate's wrappers — [`crate::CachingDevice`],
64    /// [`crate::ReadOnlyDevice`], the slice adapters — forward it
65    /// unchanged from such a parent.
66    ///
67    /// So code reading through a `dyn BlockRead` of unknown provenance
68    /// has to handle this as well as [`Error::ShortRead`] — and cannot
69    /// treat the pair as exhaustive, because a
70    /// [`crate::CallbackDevice`] reports its host's refusal as
71    /// [`Error::Io`] however the host arrived at it. Only code that knows
72    /// its device bottoms out in a [`FileDevice`] can rely on `ShortRead`
73    /// alone.
74    ///
75    /// [`FileDevice`]: crate::FileDevice
76    OutOfBounds { offset: u64, len: u64, size: u64 },
77    /// Driver-specific error lifted to the trait boundary. Each driver's
78    /// internal error type implements `Into<Error>` via this variant.
79    Custom(String),
80}
81
82impl fmt::Display for Error {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            Error::Io(e) => write!(f, "io: {e}"),
86            Error::ShortRead { offset, want, got } => {
87                write!(f, "short read at {offset}: wanted {want} got {got}")
88            }
89            Error::ReadOnly => write!(f, "device is read-only"),
90            Error::OutOfBounds { offset, len, size } => {
91                write!(f, "{offset}+{len} past device size {size}")
92            }
93            Error::Custom(s) => f.write_str(s),
94        }
95    }
96}
97
98impl std::error::Error for Error {
99    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
100        match self {
101            Error::Io(e) => Some(e),
102            _ => None,
103        }
104    }
105}
106
107impl From<io::Error> for Error {
108    fn from(e: io::Error) -> Self {
109        Error::Io(e)
110    }
111}
112
113pub type Result<T> = std::result::Result<T, Error>;