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