Skip to main content

fs_ext4/
error.rs

1//! Errors returned by the ext4rs driver.
2
3use std::io;
4
5#[derive(Debug)]
6pub enum Error {
7    /// Underlying device I/O failure.
8    Io(io::Error),
9    /// Magic number mismatch — not an ext4 filesystem.
10    BadMagic { found: u16, expected: u16 },
11    /// On-disk structure failed checksum validation.
12    BadChecksum { what: &'static str },
13    /// Filesystem uses an INCOMPAT feature we don't implement.
14    UnsupportedIncompat(u32),
15    /// Filesystem uses a RO_COMPAT feature we don't implement (read-only mount required anyway).
16    UnsupportedRoCompat(u32),
17    /// Path component or inode not found.
18    NotFound,
19    /// Path resolution hit a non-directory mid-walk.
20    NotADirectory,
21    /// Operation refused because the target is a directory and the syscall
22    /// only operates on regular files (POSIX EISDIR — used by `unlink(2)`,
23    /// `truncate(2)`, etc).
24    IsADirectory,
25    /// Target already exists (POSIX EEXIST — used by `create(2)`, `mkdir(2)`,
26    /// `rename(2)` when no-replace is requested).
27    AlreadyExists,
28    /// Directory must be empty (POSIX ENOTEMPTY — used by `rmdir(2)`,
29    /// `rename(2)` replacing a non-empty dir).
30    DirectoryNotEmpty,
31    /// Write attempted on a device opened read-only (POSIX EROFS).
32    ReadOnly,
33    /// Path component longer than EXT4_NAME_LEN (255 bytes) — POSIX
34    /// ENAMETOOLONG.
35    NameTooLong,
36    /// Container (xattr in-inode region, xattr block, etc.) has no free
37    /// space for the requested entry — POSIX ENOSPC.
38    NoSpaceLeftOnDevice,
39    /// Caller passed a malformed argument or attempted a semantically
40    /// invalid mutation (POSIX EINVAL — e.g. truncate-grow, moving a dir
41    /// into its own subtree, operating on a legacy non-EXTENTS inode).
42    InvalidArgument(&'static str),
43    /// Inode number is invalid (0, > total inodes, etc.).
44    InvalidInode(u32),
45    /// Block number is invalid (> total blocks).
46    InvalidBlock(u64),
47    /// Read/write past end of file.
48    OutOfBounds,
49    /// Extent tree structure is corrupt.
50    CorruptExtentTree(&'static str),
51    /// Directory entry corrupt (rec_len out of range, name too long, etc.).
52    CorruptDirEntry(&'static str),
53    /// Generic spec-violation error.
54    Corrupt(&'static str),
55}
56
57impl From<io::Error> for Error {
58    fn from(e: io::Error) -> Self {
59        Error::Io(e)
60    }
61}
62
63impl std::fmt::Display for Error {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        match self {
66            Error::Io(e) => write!(f, "I/O error: {e}"),
67            Error::BadMagic { found, expected } => {
68                write!(f, "bad magic: 0x{found:04x} (expected 0x{expected:04x})")
69            }
70            Error::BadChecksum { what } => write!(f, "{what} checksum mismatch"),
71            Error::UnsupportedIncompat(bits) => {
72                write!(f, "unsupported INCOMPAT features: 0x{bits:08x}")
73            }
74            Error::UnsupportedRoCompat(bits) => {
75                write!(f, "unsupported RO_COMPAT features: 0x{bits:08x}")
76            }
77            Error::NotFound => write!(f, "not found"),
78            Error::NotADirectory => write!(f, "not a directory"),
79            Error::IsADirectory => write!(f, "is a directory"),
80            Error::AlreadyExists => write!(f, "already exists"),
81            Error::DirectoryNotEmpty => write!(f, "directory not empty"),
82            Error::ReadOnly => write!(f, "read-only filesystem"),
83            Error::NameTooLong => write!(f, "name too long"),
84            Error::NoSpaceLeftOnDevice => write!(f, "no space left on device"),
85            Error::InvalidArgument(msg) => write!(f, "invalid argument: {msg}"),
86            Error::InvalidInode(n) => write!(f, "invalid inode number {n}"),
87            Error::InvalidBlock(n) => write!(f, "invalid block number {n}"),
88            Error::OutOfBounds => write!(f, "out of bounds"),
89            Error::CorruptExtentTree(msg) => write!(f, "corrupt extent tree: {msg}"),
90            Error::CorruptDirEntry(msg) => write!(f, "corrupt directory entry: {msg}"),
91            Error::Corrupt(msg) => write!(f, "corrupt: {msg}"),
92        }
93    }
94}
95
96impl std::error::Error for Error {}
97
98pub type Result<T> = std::result::Result<T, Error>;
99
100impl Error {
101    /// Map an error to a POSIX errno suitable for the C ABI / FSKit.
102    ///
103    /// Values are macOS POSIX errno numbers (which match Linux for every
104    /// code used here — the divergence only starts at ENOTSUP).
105    pub fn to_errno(&self) -> i32 {
106        match self {
107            Error::Io(e) => e.raw_os_error().unwrap_or(EIO),
108            Error::NotFound => ENOENT,
109            Error::NotADirectory => ENOTDIR,
110            Error::IsADirectory => EISDIR,
111            Error::AlreadyExists => EEXIST,
112            Error::DirectoryNotEmpty => ENOTEMPTY,
113            Error::ReadOnly => EROFS,
114            Error::NameTooLong => ENAMETOOLONG,
115            Error::NoSpaceLeftOnDevice => ENOSPC,
116            Error::InvalidArgument(_) => EINVAL,
117            Error::InvalidInode(_) | Error::InvalidBlock(_) | Error::OutOfBounds => EINVAL,
118            Error::BadMagic { .. }
119            | Error::BadChecksum { .. }
120            | Error::CorruptExtentTree(_)
121            | Error::CorruptDirEntry(_)
122            | Error::Corrupt(_) => EIO,
123            Error::UnsupportedIncompat(_) | Error::UnsupportedRoCompat(_) => ENOTSUP,
124        }
125    }
126}
127
128/// POSIX errno values (macOS). Also match Linux for all codes referenced here
129/// except ENOTSUP (Linux uses 95, macOS uses 45). We target FSKit on macOS.
130pub mod errno {
131    pub const ENOENT: i32 = 2;
132    pub const EIO: i32 = 5;
133    pub const EEXIST: i32 = 17;
134    pub const ENOTDIR: i32 = 20;
135    pub const EISDIR: i32 = 21;
136    pub const EINVAL: i32 = 22;
137    pub const EROFS: i32 = 30;
138    pub const ENOSPC: i32 = 28;
139    pub const ENAMETOOLONG: i32 = 63; // macOS POSIX value
140    pub const ENOTSUP: i32 = 45;
141    pub const ENOTEMPTY: i32 = 66; // macOS POSIX value
142    pub const ENOSYS: i32 = 78; // macOS POSIX value (Linux: 38). Surfaced
143                                // by capi when a feature isn't implemented yet.
144}
145
146use errno::*;