fs_ext4/lib.rs
1//! ext4rs — pure-Rust ext4 filesystem driver.
2//!
3//! Exposes a stable C ABI (`fs_ext4_*`) via [`capi`] so FFI consumers
4//! (Swift/C/Go/…) can link `libfs_ext4.a` and `#include "fs_ext4.h"`.
5//!
6//! Architecture (reading; the write path is in [`fs`], [`alloc`],
7//! [`journal_writer`] and the `*_mut` modules):
8//! - [`block_io`] — abstract trait for reading device blocks
9//! - [`superblock`] — parse + validate the on-disk superblock
10//! - [`features`] — feature flag inventory (COMPAT/INCOMPAT/RO_COMPAT)
11//! - [`bgd`] — block group descriptor parsing
12//! - [`inode`] — inode + extra fields parsing
13//! - [`extent`] — extent tree traversal (leaf/internal nodes, uninitialized extents)
14//! - [`dir`] — directory entries (linear and HTree)
15//! - [`hash`] — htree hash functions (legacy / half_md4 / tea)
16//! - [`fs`] — top-level filesystem handle, file/dir lookup, read API
17//! - [`capi`] — C ABI exports matching `include/fs_ext4.h`
18
19// `doc_lazy_continuation` disagrees with the existing numbered-list
20// indentation in several module preambles; the content is fine.
21#![allow(clippy::doc_lazy_continuation)]
22
23pub mod acl;
24pub mod alloc;
25pub mod bgd;
26pub mod block_cache;
27pub mod block_io;
28pub mod casefold;
29pub mod checksum;
30pub mod dir;
31pub mod ea_inode;
32pub mod error;
33pub mod extent;
34pub mod extent_mut;
35pub mod features;
36pub mod file_io;
37pub mod file_mut;
38pub mod fs;
39pub mod fs_core_bridge;
40pub mod fsck;
41pub mod hash;
42pub mod htree;
43pub mod htree_mut;
44pub mod indirect;
45pub mod indirect_mut;
46pub mod inline_data;
47pub(crate) mod inline_mut;
48pub mod inode;
49pub mod jbd2;
50pub mod journal;
51pub mod journal_apply;
52pub mod journal_writer;
53pub mod mkfs;
54pub mod path;
55pub mod runtime;
56pub mod superblock;
57pub mod transaction;
58pub mod verify;
59pub mod xattr;
60
61// C ABI exports — surface defined in `include/fs_ext4.h`.
62pub mod capi;
63
64pub use error::{Error, Result};
65pub use fs::{Filesystem, InodeRef};
66pub use superblock::Superblock;
67
68// DOES THIS BUILD ACTUALLY TRAP AN ARITHMETIC OVERFLOW?
69//
70// Inline in `lib.rs` rather than a module of its own under `src/`: a
71// separate file hangs off one `mod` line, and losing that line leaves
72// the file present, uncompiled and asserting nothing, with no lint to
73// say so. Inline, there is no declaration to lose. It cannot live in
74// `tests/` either -- it has to be part of the library target the debug
75// step in ci.yml builds, because the question it answers is about that
76// build specifically.
77#[cfg(test)]
78mod overflow_checks {
79 /// Set by the debug step in `ci.yml`, and by nothing else.
80 ///
81 /// The release step must NOT set it: overflow checks are off there
82 /// deliberately, because that is what ships.
83 const HANDSHAKE: &str = "EXPECT_OVERFLOW_CHECKS";
84
85 /// Perform an overflow and report whether the program was stopped.
86 ///
87 /// The only question that matters, and the only one a text scan of
88 /// Cargo.toml or the workflow cannot answer on its own: whichever
89 /// spelling of "the checks are off" might exist -- a manifest key, a
90 /// `CARGO_PROFILE_*` variable, a `.cargo/config.toml` -- this asks
91 /// the build directly instead of enumerating them.
92 ///
93 /// The panic hook is left alone. It is process-wide, and the other
94 /// library tests run beside this one on other threads, so silencing
95 /// it here would silence their failures too (#331). The test harness
96 /// already captures what the default hook prints for this thread and
97 /// shows it only if this test fails; tests/scripts/test-no-panic-hook.sh
98 /// keeps `set_hook` out of `src/`.
99 fn this_build_traps_an_overflow() -> bool {
100 std::panic::catch_unwind(|| {
101 let big = std::hint::black_box(u64::MAX);
102 std::hint::black_box(big + 1);
103 })
104 .is_err()
105 }
106
107 /// When the gate says it built a profile that traps, check that it
108 /// did.
109 ///
110 /// With `HANDSHAKE` unset this asserts nothing -- the shape of a
111 /// test that passes because its fixture is missing -- and it is not
112 /// guarded here because it cannot be: a build cannot tell whether it
113 /// was supposed to be the checking one. It is guarded in
114 /// `tests/ci_profile.rs`, which reads `ci.yml` and refuses if no
115 /// `cargo test` there runs without `--release` while setting this
116 /// variable.
117 #[test]
118 fn the_build_the_gate_asked_to_check_does_check() {
119 let asked = match std::env::var(HANDSHAKE) {
120 Ok(value) if !value.is_empty() => value,
121 _ => return,
122 };
123
124 assert!(
125 this_build_traps_an_overflow(),
126 "{HANDSHAKE}={asked} was set, so this run is the one that is \
127 supposed to panic on arithmetic overflow -- and it did not. \
128 The debug step is running and blind, which is the exact \
129 state it exists to rule out."
130 );
131 }
132}