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