Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 114 additions & 37 deletions crates/bux-e2fs/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Error types for ext4 filesystem operations.

use std::fmt;

/// Errors returned by ext4 operations.
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
Expand All @@ -9,12 +11,12 @@
)]
pub enum Error {
/// A libext2fs function returned a non-zero error code.
#[error("{op}: {}", describe_ext2fs_error(*.code))]
#[error("{op}: {code}")]
Ext2fs {
/// Name of the libext2fs operation that failed.
op: &'static str,
/// The raw `errcode_t` value.
code: i64,
/// The decoded libext2fs error code.
code: Ext2Code,
},

/// A path could not be converted to a C string.
Expand All @@ -29,42 +31,117 @@ pub enum Error {
/// Convenience alias for `std::result::Result<T, Error>`.
pub type Result<T> = std::result::Result<T, Error>;

/// Maps common libext2fs error codes to human-readable descriptions.
/// A libext2fs error code.
///
/// Codes live in the `ext2_err.h` error table as `EXT2_ET_BASE + offset`,
/// where `EXT2_ET_BASE` is 2133571328 (`0x7F2BB700`). Functions in
/// `misc/create_inode.c` may instead propagate a bare host `errno`; those are
/// represented by [`Ext2Code::Errno`].
///
/// Error codes are defined in `ext2_err.h` (base 2133571328 = 0x7F2C0000).
fn describe_ext2fs_error(code: i64) -> String {
const BASE: i64 = 2_133_571_328;
if let Ok(errno) = i32::try_from(code)
&& errno > 0
&& code < BASE
{
return format!(
"{} (errno {errno})",
std::io::Error::from_raw_os_error(errno)
);
/// Only codes this crate matches on in control flow get their own variant.
/// Everything else keeps its raw value in [`Ext2Code::Other`] and is described
/// by the [`fmt::Display`] impl. The enum is `#[non_exhaustive]` so a code can
/// be promoted out of `Other` without a breaking change.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ext2Code {
/// `EXT2_ET_NO_INODE_BITMAP` (offset 39) — the inode bitmap is not loaded.
NoInodeBitmap,
/// `EXT2_ET_NO_BLOCK_BITMAP` (offset 40) — the block bitmap is not loaded.
NoBlockBitmap,
/// `EXT2_ET_TOOSMALL` (offset 44) — the filesystem is too small.
TooSmall,
/// `EXT2_ET_DIR_EXISTS` (offset 79) — the directory entry already exists.
DirExists,
/// `EXT2_ET_FILE_EXISTS` (offset 155) — the file already exists.
FileExists,
/// A bare host `errno` propagated by `misc/create_inode.c`.
Errno(i32),
/// Any other libext2fs code, kept verbatim.
Other(i64),
}

impl Ext2Code {
/// `EXT2_ET_BASE` — first entry of the libext2fs error table.
pub const BASE: i64 = 2_133_571_328;

/// Classifies a raw `errcode_t` returned by libext2fs.
#[must_use]
#[allow(
clippy::cast_possible_truncation,
reason = "guarded by 0 < code < BASE, and BASE < i32::MAX"
)]
pub const fn from_raw(code: i64) -> Self {
// create_inode.c returns bare errno values for host-side I/O failures.
if code > 0 && code < Self::BASE {
return Self::Errno(code as i32);
}
match code - Self::BASE {
39 => Self::NoInodeBitmap,
40 => Self::NoBlockBitmap,
44 => Self::TooSmall,
79 => Self::DirExists,
155 => Self::FileExists,
_ => Self::Other(code),
}
}

/// The raw `errcode_t` this code was decoded from.
#[must_use]
pub const fn raw(self) -> i64 {
match self {
Self::NoInodeBitmap => Self::BASE + 39,
Self::NoBlockBitmap => Self::BASE + 40,
Self::TooSmall => Self::BASE + 44,
Self::DirExists => Self::BASE + 79,
Self::FileExists => Self::BASE + 155,
Self::Errno(errno) => errno as i64,
Self::Other(code) => code,
}
}
}

match code - BASE {
1 => "bad magic number in superblock".into(),
2 => "filesystem revision too high".into(),
4 => "illegal block number".into(),
5 => "illegal inode number".into(),
6 => "internal error in ext2fs_open_icount".into(),
7 => "cannot write to an fs opened read-only".into(),
8 => "block bitmap not loaded".into(),
9 => "inode bitmap not loaded".into(),
10 => "no free blocks".into(),
11 => "no free inodes".into(),
12 => "directory block not found".into(),
16 => "inode already allocated".into(),
17 => "block already allocated".into(),
22 => "filesystem not open".into(),
23 => "device is read-only".into(),
24 => "directory corrupted".into(),
25 => "short read".into(),
26 => "short write".into(),
28 => "filesystem too large".into(),
85 => "journal too small".into(),
_ => format!("libext2fs error {code:#x}"),
impl fmt::Display for Ext2Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::NoInodeBitmap => f.write_str("inode bitmap not loaded"),
Self::NoBlockBitmap => f.write_str("block bitmap not loaded"),
Self::TooSmall => f.write_str("filesystem too small for the requested operation"),
Self::DirExists => f.write_str("directory entry already exists"),
Self::FileExists => f.write_str("file already exists"),
Self::Errno(errno) => write!(
f,
"{} (errno {errno})",
std::io::Error::from_raw_os_error(errno)
),
// Offsets verified against ext2_err.h (e2fsprogs 1.47.1).
Self::Other(code) => match code - Self::BASE {
19 => f.write_str("bad magic number in superblock"),
20 => f.write_str("filesystem revision too high"),
21 => f.write_str("cannot write to a read-only filesystem"),
22 => f.write_str("group descriptors read failure"),
23 => f.write_str("group descriptors write failure"),
35 => f.write_str("directory corrupted"),
36 => f.write_str("short read"),
37 => f.write_str("short write"),
38 => f.write_str("no space left in directory"),
41 => f.write_str("illegal inode number"),
42 => f.write_str("illegal block number"),
60 => f.write_str("corrupt superblock"),
67 => f.write_str("unsupported feature"),
68 => f.write_str("unsupported read-only feature"),
70 => f.write_str("out of memory"),
71 => f.write_str("invalid argument"),
72 => f.write_str("block allocation failed"),
73 => f.write_str("inode allocation failed"),
74 => f.write_str("not a directory"),
76 => f.write_str("file not found"),
78 => f.write_str("directory block not found"),
80 => f.write_str("operation not implemented"),
82 => f.write_str("file too big"),
85 => f.write_str("journal too small"),
_ => write!(f, "libext2fs error {code:#x}"),
},
}
}
}
15 changes: 10 additions & 5 deletions crates/bux-e2fs/src/ext4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use std::ffi::CString;
use std::fs::OpenOptions;
use std::path::Path;

use crate::error::{Error, Result};
use crate::error::{Error, Ext2Code, Result};
use crate::sys;

/// Block size for an ext4 filesystem.
Expand Down Expand Up @@ -315,7 +315,6 @@ impl Filesystem {
/// Returns an error if a directory cannot be created for a reason other
/// than it already existing.
pub fn mkdir_p(&mut self, name: &str) -> Result<()> {
const EXT2_ET_DIR_EXISTS: i64 = 2_133_571_328 + 79;
let mut prefix = String::new();
for component in name.split('/') {
if component.is_empty() {
Expand All @@ -326,8 +325,11 @@ impl Filesystem {
}
prefix.push_str(component);
match self.mkdir(&prefix) {
Ok(()) => {}
Err(Error::Ext2fs { code, .. }) if code == EXT2_ET_DIR_EXISTS => {}
Ok(())
| Err(Error::Ext2fs {
code: Ext2Code::DirExists,
..
}) => {}
Err(e) => return Err(e),
}
}
Expand Down Expand Up @@ -664,7 +666,10 @@ const fn check(op: &'static str, code: sys::errcode_t) -> Result<()> {
if code == 0 {
Ok(())
} else {
Err(Error::Ext2fs { op, code })
Err(Error::Ext2fs {
op,
code: Ext2Code::from_raw(code),
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/bux-e2fs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub mod sys;
mod error;
mod ext4;

pub use error::{Error, Result};
pub use error::{Error, Ext2Code, Result};
pub use ext4::{
BlockSize, CreateOptions, Ext4Builder, FileType, Filesystem, create_from_dir,
estimate_image_size, inject_file,
Expand Down