Skip to main content

fs_ext4/
error.rs

1//! Errors returned by the ext4rs driver.
2
3use std::io;
4
5/// `#[non_exhaustive]` (#120): a new way to fail is a new variant, and without
6/// this every variant added was a break for any caller matching exhaustively.
7/// A `match` outside this crate needs a `_` arm.
8#[derive(Debug)]
9#[non_exhaustive]
10pub enum Error {
11    /// Underlying device I/O failure.
12    Io(io::Error),
13    /// Magic number mismatch — not an ext4 filesystem.
14    BadMagic { found: u16, expected: u16 },
15    /// On-disk structure failed checksum validation.
16    BadChecksum { what: &'static str },
17    /// Filesystem uses an INCOMPAT feature we don't implement.
18    UnsupportedIncompat(u32),
19    /// Filesystem uses a RO_COMPAT feature we don't implement (read-only mount required anyway).
20    UnsupportedRoCompat(u32),
21    /// Path component or inode not found.
22    NotFound,
23    /// Path resolution hit a non-directory mid-walk.
24    NotADirectory,
25    /// Operation refused because the target is a directory and the syscall
26    /// only operates on regular files (POSIX EISDIR — used by `unlink(2)`,
27    /// `truncate(2)`, etc).
28    IsADirectory,
29    /// Target already exists (POSIX EEXIST — used by `create(2)`, `mkdir(2)`,
30    /// `rename(2)` when no-replace is requested).
31    AlreadyExists,
32    /// Directory must be empty (POSIX ENOTEMPTY — used by `rmdir(2)`,
33    /// `rename(2)` replacing a non-empty dir).
34    DirectoryNotEmpty,
35    /// Write attempted on a device opened read-only (POSIX EROFS).
36    ReadOnly,
37    /// Path component longer than EXT4_NAME_LEN (255 bytes) — POSIX
38    /// ENAMETOOLONG.
39    NameTooLong,
40    /// Container (xattr in-inode region, xattr block, etc.) has no free
41    /// space for the requested entry — POSIX ENOSPC.
42    NoSpaceLeftOnDevice,
43    /// Caller passed a malformed argument or attempted a semantically
44    /// invalid mutation (POSIX EINVAL — e.g. truncate-grow, moving a dir
45    /// into its own subtree, operating on a legacy non-EXTENTS inode).
46    InvalidArgument(&'static str),
47    /// Inode number is invalid (0, > total inodes, etc.).
48    InvalidInode(u32),
49    /// Block number is invalid (> total blocks).
50    InvalidBlock(u64),
51    /// Read/write past end of file.
52    OutOfBounds,
53    /// Extent tree structure is corrupt.
54    CorruptExtentTree(&'static str),
55    /// Directory entry corrupt (rec_len out of range, name too long, etc.).
56    CorruptDirEntry(&'static str),
57    /// Generic spec-violation error.
58    Corrupt(&'static str),
59
60    /// The inode's link count has no room for another link (POSIX
61    /// EMLINK): a file at `MAX_LINKS` (65000) links, or a directory
62    /// with that many on a volume without `DIR_NLINK`.
63    TooManyLinks,
64
65    /// A structure this driver can read but cannot correctly modify.
66    /// Distinct from [`Error::ReadOnly`], which is about the mount, and
67    /// from the `Unsupported*` feature errors, which are about the volume:
68    /// this is one operation on one object being refused because doing it
69    /// would leave the filesystem worse than not doing it.
70    Unsupported(&'static str),
71
72    /// A write refused because the volume's journal holds committed
73    /// transactions this mount has not yet replayed onto the device: a
74    /// lazy mount before [`crate::fs::Filesystem::replay_journal_if_dirty`].
75    /// Writing first would commit over the unreplayed log and lose it.
76    /// Maps to EROFS: the mount is read-only until the replay runs.
77    JournalNotReplayed,
78    /// An inode handle no longer names the file it was taken from (POSIX
79    /// ESTALE): the inode number is out of range or reserved, the inode has
80    /// been freed, or it has been freed and reused under a new generation.
81    Stale,
82}
83
84impl From<io::Error> for Error {
85    fn from(e: io::Error) -> Self {
86        Error::Io(e)
87    }
88}
89
90impl std::fmt::Display for Error {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        match self {
93            Error::Io(e) => write!(f, "I/O error: {e}"),
94            Error::BadMagic { found, expected } => {
95                write!(f, "bad magic: 0x{found:04x} (expected 0x{expected:04x})")
96            }
97            Error::BadChecksum { what } => write!(f, "{what} checksum mismatch"),
98            Error::UnsupportedIncompat(bits) => {
99                write!(f, "unsupported INCOMPAT features: 0x{bits:08x}")
100            }
101            Error::UnsupportedRoCompat(bits) => {
102                write!(f, "unsupported RO_COMPAT features: 0x{bits:08x}")
103            }
104            Error::NotFound => write!(f, "not found"),
105            Error::NotADirectory => write!(f, "not a directory"),
106            Error::IsADirectory => write!(f, "is a directory"),
107            Error::AlreadyExists => write!(f, "already exists"),
108            Error::DirectoryNotEmpty => write!(f, "directory not empty"),
109            Error::ReadOnly => write!(f, "read-only filesystem"),
110            Error::NameTooLong => write!(f, "name too long"),
111            Error::NoSpaceLeftOnDevice => write!(f, "no space left on device"),
112            Error::InvalidArgument(msg) => write!(f, "invalid argument: {msg}"),
113            Error::InvalidInode(n) => write!(f, "invalid inode number {n}"),
114            Error::InvalidBlock(n) => write!(f, "invalid block number {n}"),
115            Error::OutOfBounds => write!(f, "out of bounds"),
116            Error::CorruptExtentTree(msg) => write!(f, "corrupt extent tree: {msg}"),
117            Error::CorruptDirEntry(msg) => write!(f, "corrupt directory entry: {msg}"),
118            Error::Corrupt(msg) => write!(f, "corrupt: {msg}"),
119            Error::Unsupported(msg) => write!(f, "unsupported: {msg}"),
120            Error::JournalNotReplayed => {
121                write!(f, "read-only until the dirty journal is replayed")
122            }
123            Error::Stale => write!(f, "stale inode handle"),
124            Error::TooManyLinks => write!(f, "too many links"),
125        }
126    }
127}
128
129impl std::error::Error for Error {}
130
131pub type Result<T> = std::result::Result<T, Error>;
132
133impl Error {
134    /// Map an error to a POSIX errno suitable for the C ABI / FSKit.
135    ///
136    /// Values are macOS POSIX errno numbers (which match Linux for every
137    /// code used here — the divergence only starts at ENOTSUP).
138    pub fn to_errno(&self) -> i32 {
139        match self {
140            Error::Io(e) => e.raw_os_error().unwrap_or(EIO),
141            Error::NotFound => ENOENT,
142            Error::NotADirectory => ENOTDIR,
143            Error::IsADirectory => EISDIR,
144            Error::AlreadyExists => EEXIST,
145            Error::DirectoryNotEmpty => ENOTEMPTY,
146            Error::ReadOnly | Error::JournalNotReplayed => EROFS,
147            Error::NameTooLong => ENAMETOOLONG,
148            Error::NoSpaceLeftOnDevice => ENOSPC,
149            Error::TooManyLinks => EMLINK,
150            Error::InvalidArgument(_) => EINVAL,
151            Error::InvalidInode(_) | Error::InvalidBlock(_) | Error::OutOfBounds => EINVAL,
152            Error::BadMagic { .. }
153            | Error::BadChecksum { .. }
154            | Error::CorruptExtentTree(_)
155            | Error::CorruptDirEntry(_)
156            | Error::Corrupt(_) => EIO,
157            Error::UnsupportedIncompat(_)
158            | Error::UnsupportedRoCompat(_)
159            | Error::Unsupported(_) => ENOTSUP,
160            Error::Stale => ESTALE,
161        }
162    }
163}
164
165/// POSIX errno values (macOS). Also match Linux for all codes referenced here
166/// except ENOTSUP (Linux uses 95, macOS uses 45). We target FSKit on macOS.
167pub mod errno {
168    pub const ENOENT: i32 = 2;
169    pub const EIO: i32 = 5;
170    pub const EEXIST: i32 = 17;
171    pub const ENOTDIR: i32 = 20;
172    pub const EISDIR: i32 = 21;
173    pub const EINVAL: i32 = 22;
174    pub const EROFS: i32 = 30;
175    pub const EMLINK: i32 = 31;
176    pub const ERANGE: i32 = 34;
177    pub const ENOSPC: i32 = 28;
178    pub const ENAMETOOLONG: i32 = 63; // macOS POSIX value
179    pub const ENOTSUP: i32 = 45;
180    pub const ENOTEMPTY: i32 = 66; // macOS POSIX value
181    pub const ESTALE: i32 = 70; // macOS POSIX value (Linux: 116)
182    pub const ENOSYS: i32 = 78; // macOS POSIX value (Linux: 38). Surfaced
183                                // by capi when a feature isn't implemented yet.
184}
185
186use errno::*;