pub struct Filesystem {
pub dev: Arc<dyn BlockDevice>,
pub sb: Superblock,
pub groups: Vec<BlockGroupDescriptor>,
pub csum: Checksummer,
pub flavor: FsFlavor,
pub journal: Option<Mutex<JournalWriter>>,
/* private fields */
}Fields§
§dev: Arc<dyn BlockDevice>§sb: Superblock§groups: Vec<BlockGroupDescriptor>§csum: Checksummer§flavor: FsFlavorDialect detected at mount time from the superblock’s feature flags. Drives runtime dispatch where ext2 / ext3 / ext4 differ — most notably the inode block-mapping scheme (extent vs indirect) used when allocating new inodes.
journal: Option<Mutex<JournalWriter>>Live-write journal writer, present iff the FS has a journal AND
the device is writable. None for read-only mounts and for ext2-
style images. Locked per-op so mutating capi calls serialize on
the JBD2 sequence cursor.
Implementations§
Source§impl Filesystem
impl Filesystem
Sourcepub fn mount(dev: Arc<dyn BlockDevice>) -> Result<Self>
pub fn mount(dev: Arc<dyn BlockDevice>) -> Result<Self>
Mount the ext4 filesystem on dev. Read-only unless the device reports
is_writable(), in which case a dirty journal is replayed before
returning so callers see a consistent on-disk state.
When RO_COMPAT_METADATA_CSUM is set, the superblock checksum is
verified — failure aborts the mount with Error::BadChecksum.
Sourcepub fn mount_lazy(dev: Arc<dyn BlockDevice>) -> Result<Self>
pub fn mount_lazy(dev: Arc<dyn BlockDevice>) -> Result<Self>
Like mount, but skips the mount-time journal replay even when the
device is writable. The caller is responsible for invoking
Filesystem::replay_journal_if_dirty once the underlying write
path is actually ready to service writes (e.g. in the FSKit case the
kernel-level write FD on FSBlockDeviceResource only becomes
writable AFTER loadResource returns successfully — replaying mid-
loadResource produces EIO).
Until replay runs, reads observe the on-disk pre-replay state and
any write through this handle will fail (the journal still says
dirty). This is the lazy/deferred-replay sibling of mount; for
most callers mount is correct.
Sourcepub fn replay_journal_if_dirty(&self) -> Result<usize>
pub fn replay_journal_if_dirty(&self) -> Result<usize>
Run journal replay now if the journal is dirty. Idempotent — calling
this on a clean (or read-only) volume is a no-op that returns 0.
Designed to pair with Filesystem::mount_lazy, but safe to call
on any handle.
Sourcepub fn orphan_list(&self) -> Result<Vec<u32>>
pub fn orphan_list(&self) -> Result<Vec<u32>>
Phase 6.1 — walk the orphan inode chain rooted at s_last_orphan
and return its members in chain order.
Each orphan inode is a unlink-while-open candidate: its data
blocks should be reclaimed by recovery. The chain is encoded by
overloading i_dtime as “next orphan inode number”; the chain
terminates when dtime == 0. We cap at inodes_count to avoid
runaway loops on cycle-corrupted images.
Read-only (no recovery yet — that’s Phase 6.2). Returns Ok([])
when there are no orphans.
Sourcepub fn recover_orphans(&self) -> Result<usize>
pub fn recover_orphans(&self) -> Result<usize>
Phase 6.2 — orphan replay. For each inode on the
s_last_orphan chain, free its data blocks + inode-bitmap slot,
zero its inode body (with i_dtime = now), and clear
s_last_orphan. Runs as ONE multi-block journaled transaction
so a crash mid-recovery either commits all the frees or none of
them.
Returns the number of orphan inodes reclaimed. No-op (returns 0) when the chain is empty or the device is read-only.
Designed to be called from the mount path AFTER journal replay, so the orphans we’re about to reclaim are guaranteed not still in use by an in-flight kernel-level transaction.
Sourcepub fn read_block(&self, block_num: u64) -> Result<Vec<u8>>
pub fn read_block(&self, block_num: u64) -> Result<Vec<u8>>
Read a whole block by its logical block number. Routes through
self.dev, which at mount time is wrapped in a CachedDevice —
so this single call benefits from the buffer cache that holds
post-commit, pre-checkpoint journaled writes.
Sourcepub fn read_inode_raw(&self, ino: u32) -> Result<Vec<u8>>
pub fn read_inode_raw(&self, ino: u32) -> Result<Vec<u8>>
Read raw inode bytes for a given inode number (does not parse).
Sourcepub fn read_inode_verified(&self, ino: u32) -> Result<(Inode, Vec<u8>)>
pub fn read_inode_verified(&self, ino: u32) -> Result<(Inode, Vec<u8>)>
Read + parse + checksum-verify an inode in one shot.
When RO_COMPAT_METADATA_CSUM is enabled the inode CRC32C is checked
(salted by inode number + generation per ext4 spec). A mismatch
returns Error::BadChecksum { what: "inode" }.
Sourcepub fn map_inode_logical(
&self,
inode: &Inode,
logical_block: u64,
) -> Result<Option<u64>>
pub fn map_inode_logical( &self, inode: &Inode, logical_block: u64, ) -> Result<Option<u64>>
Map a logical block within inode to its physical block, choosing
between the extent tree and the legacy direct/indirect scheme based
on EXT4_EXTENTS_FL. Returns None for sparse holes and (for the
extent path) uninitialised extents — callers wanting zeros there
must handle the None case explicitly.
This is the per-inode dispatcher every directory traversal /
extent-walking call site should use instead of touching
extent::map_logical directly — without it, an ext2/3 inode with
raw block pointers in i_block gets misparsed as an extent header
(yielding CorruptExtentTree("bad extent header magic")).
The indirect path internally maintains its own block cache for the duration of the call; sequential lookups via repeated calls don’t share that cache (file_io’s read paths build a longer-lived cache to amortize across blocks).
Sourcepub fn write_inode_raw(&self, ino: u32, raw: &[u8]) -> Result<()>
pub fn write_inode_raw(&self, ino: u32, raw: &[u8]) -> Result<()>
Write the given raw inode bytes back to disk. Read-only devices return
the default Error::Corrupt from BlockDevice::write_at.
Not checksum-aware: callers that update fields affecting the inode
CRC32C (anything except checksum_lo / checksum_hi) must recompute
- patch the checksum into
rawbefore calling this. Not wrapped in a journal transaction — see E11 /journal_applyfor the journaled version. Use only when the caller has the full write-ordering story under control.
Sourcepub fn patch_inode_size_and_blocks(
raw: &mut [u8],
new_size: u64,
new_block_count: u64,
) -> Result<()>
pub fn patch_inode_size_and_blocks( raw: &mut [u8], new_size: u64, new_block_count: u64, ) -> Result<()>
Patch fields in a raw inode image: size, blocks_count. Leaves all
other bytes (including the extent tree header + entries in i_block)
intact. new_block_count is in 512-byte sectors per spec (same
convention as Inode::blocks).
Sourcepub fn patch_inode_block_area(raw: &mut [u8], new_root: &[u8]) -> Result<()>
pub fn patch_inode_block_area(raw: &mut [u8], new_root: &[u8]) -> Result<()>
Overwrite the 60-byte i_block area of an inode image with new_root.
Used when an extent-tree mutation changes the inline root.
Sourcepub fn apply_truncate_shrink(&self, ino: u32, new_size: u64) -> Result<()>
pub fn apply_truncate_shrink(&self, ino: u32, new_size: u64) -> Result<()>
Shrink a file to new_size. Composes file_mut::plan_truncate_shrink
(extent-tree updates + freed-block ranges) with actual disk writes —
rewrites the inode and zeros the freed bitmap bits.
Journaled. The inode write, the bitmap writes, the BGD and the
superblock accumulate into one BlockBuffer and commit as a
single transaction, so they are atomic with respect to a crash.
This said “Not journaled … safe only in a test scratch image”, and promised the transaction as future work. The future work landed; the warning outlived it and was steering callers away from an API that is safe.
Sourcepub fn apply_truncate_grow(&self, ino: u32, new_size: u64) -> Result<()>
pub fn apply_truncate_grow(&self, ino: u32, new_size: u64) -> Result<()>
Extend a file to new_size. The new range is a sparse hole — ext4’s
extent tree treats unmapped logical blocks as zeros, so no extent
mutation and no block allocation are required. Only i_size,
i_mtime, i_ctime, and the inode checksum change.
Caller (capi dispatch) guarantees new_size >= inode.size. If
new_size == inode.size this is a no-op that still bumps the
timestamps — matches truncate(2) semantics.
Sourcepub fn apply_fallocate_keep_size(
&self,
ino: u32,
offset: u64,
len: u64,
) -> Result<()>
pub fn apply_fallocate_keep_size( &self, ino: u32, offset: u64, len: u64, ) -> Result<()>
Phase 2.2: fallocate(FALLOC_FL_KEEP_SIZE) — preallocate blocks
in the byte range [offset, offset+len) as uninitialized
extents. The blocks are reserved (count against i_blocks) but
reads return zeros until they’re written. i_size is left
unchanged per KEEP_SIZE semantics.
v1 limitations:
- Range must be entirely unmapped — partially-overlapping ranges
return
Error::InvalidArgument. (Splitting around existing extents is a follow-up.) - Single contiguous physical allocation. If the bitmap can’t
serve
ceil(len / block_size)contiguous blocks, returnsError::Corrupt("no group has a contiguous free run..."). - Extent insertion must succeed against the inline-root depth-0 tree (or trigger the existing depth-1 promotion). Multi-level trees aren’t yet supported.
Sourcepub fn apply_fallocate_punch_hole(
&self,
ino: u32,
offset: u64,
len: u64,
) -> Result<()>
pub fn apply_fallocate_punch_hole( &self, ino: u32, offset: u64, len: u64, ) -> Result<()>
Phase 2.3 — fallocate(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE).
Frees the data blocks underlying [offset, offset+len), splitting
straddling extents as needed. Reads of the punched range return
zeros (sparse hole) thereafter; i_size is unchanged.
v1 limits:
- Depth-0 inline-root extent trees only. Surviving entries must
fit in 4 slots (the inline-root capacity); anything larger
returns
Corrupt(...). A real punch on a heavily-fragmented file may need depth ≥ 1, which is a Phase 4 follow-up. - Indirect-block (ext2/3) inodes return EINVAL — punch is an ext4-specific kernel API.
Sourcepub fn apply_fallocate_zero_range(
&self,
ino: u32,
offset: u64,
len: u64,
) -> Result<()>
pub fn apply_fallocate_zero_range( &self, ino: u32, offset: u64, len: u64, ) -> Result<()>
Phase 2.4 — fallocate(FALLOC_FL_ZERO_RANGE). Logically zero the
byte range [offset, offset+len) without writing actual data.
Implemented as punch-hole + KEEP_SIZE preallocate of the same
range, so reads return zeros (uninitialized-extent semantics) and
future writes don’t need an allocation.
Two separate transactions today (punch then alloc); a future optimization could fold them into one.
Sourcepub fn apply_chmod(&self, path: &str, mode: u16) -> Result<()>
pub fn apply_chmod(&self, path: &str, mode: u16) -> Result<()>
Change the permission bits on path. Only the low 12 bits of mode
(S_ISUID|S_ISGID|S_ISVTX plus rwx/rwx/rwx) are applied; the file-type
bits (S_IFMT) are preserved from the existing inode.
Updates i_ctime = now and recomputes the inode checksum on csum-
enabled mounts. Returns Error::NotFound if the path doesn’t resolve,
Error::ReadOnly on a RO mount.
Sourcepub fn apply_chown(&self, path: &str, uid: u32, gid: u32) -> Result<()>
pub fn apply_chown(&self, path: &str, uid: u32, gid: u32) -> Result<()>
Change the owner of path to (uid, gid). Both values are full
32-bit — the inode stores them as hi+lo u16 halves at different
offsets per the ext4 on-disk format. Passing u32::MAX for either
field leaves that value untouched (Linux lchown(2) convention).
Updates i_ctime = now and recomputes the inode checksum on
csum-enabled mounts.
Sourcepub fn apply_set_flags(&self, path: &str, flags: u32) -> Result<()>
pub fn apply_set_flags(&self, path: &str, flags: u32) -> Result<()>
Set the i_flags field (FS_IOC_SETFLAGS) for the inode at path.
Bumps ctime. Fails with Error::ReadOnly on read-only mounts, or
Error::InvalidArgument if the caller attempts to flip any of the
layout-critical flags managed internally (EXTENTS_FL, INLINE_DATA_FL,
EA_INODE_FL) — changing those without rewriting the inode payload would
corrupt the filesystem.
Sourcepub fn apply_removexattr(&self, path: &str, name: &str) -> Result<()>
pub fn apply_removexattr(&self, path: &str, name: &str) -> Result<()>
Remove the extended attribute named name from the inode at path.
name must carry a known namespace prefix (e.g. "user.color").
v1 scope: in-inode xattrs only. The in-inode region (bytes
between 128 + i_extra_isize and the end of the on-disk inode)
is decoded, the matching entry is dropped, and the region is
re-encoded in place. External xattr blocks (pointed at by
Search the in-inode region first, then the external xattr block. If
the external block becomes empty after removal, free it and zero
i_file_acl (matches kernel behavior — empty xattr blocks are
reaped on the spot rather than left dangling).
Returns:
Ok(())on success.Error::NotFoundif the entry isn’t present in either region.Error::InvalidArgumenton namespace-prefix issues.
Sourcepub fn apply_setxattr(&self, path: &str, name: &str, value: &[u8]) -> Result<()>
pub fn apply_setxattr(&self, path: &str, name: &str, value: &[u8]) -> Result<()>
Set (create or replace) the extended attribute name with value
on the inode at path. name must carry a known namespace prefix
(e.g. "user.com.apple.FinderInfo").
Try-order, matching the kernel:
- In-inode region — between
128 + i_extra_isizeand the end of the on-disk inode. Cheapest; no extra block. - External xattr block — when in-inode is full, fall back to a
dedicated block referenced by
i_file_acl. Allocates a fresh block when none exists, otherwise rewrites the existing one. ReturnsError::NoSpaceLeftOnDeviceif even a full block can’t hold the new layout.
Sourcepub fn apply_utimens(
&self,
path: &str,
atime_sec: i64,
atime_nsec: u32,
mtime_sec: i64,
mtime_nsec: u32,
) -> Result<()>
pub fn apply_utimens( &self, path: &str, atime_sec: i64, atime_nsec: u32, mtime_sec: i64, mtime_nsec: u32, ) -> Result<()>
Set the access + modification times on path. Mirrors POSIX
utimensat(2): atime_sec/nsec and mtime_sec/nsec each replace
the inode’s atime/mtime. ctime is bumped to now (POSIX requires
the change-time stamp on any attribute write). The TIME_OMIT
sentinel on either _sec leaves that pair unchanged (lets callers
touch just atime or just mtime).
Seconds are signed and 64-bit because that is what the format
means: the on-disk base is a signed 32-bit count, extended by the
low two bits of the matching *_extra field. A u32 here could
not express a pre-1970 date at all, and stored every date past
2038 as one in the 1900s — the base was written and the epoch
bits left zero, so the value read back 136 years early.
nsec values are the sub-second timestamp in nanoseconds and are
only written when the inode’s i_extra_isize region is large
enough to hold them (requires ≥ 160-byte inodes — the ext4 tooling
default). That same region holds the epoch bits, so on an inode
too small to carry it, a time needing them is refused rather than
silently stored as the wrong century.
Sourcepub fn apply_unlink(&self, path: &str) -> Result<()>
pub fn apply_unlink(&self, path: &str) -> Result<()>
Unlink a regular file / symlink / special file at path.
Semantics:
- Refuses to unlink a directory (use a future
apply_rmdir). - Decrements the target inode’s
i_links_count. When that reaches zero, frees every data block viaplan_truncate_shrink(size → 0), clears the inode bitmap bit, zeroes the inode body, and setsi_dtime = now. Whenlinks_count > 1we only drop the dir entry and decrement — matches POSIX unlink semantics for hard-linked files. - Mutates: parent-dir block (entry removal), target inode, block +
inode bitmaps, BGD counters, SB counters. No journaling yet —
safe only on scratch images (same caveat as
apply_truncate_shrink).
Returns Error::NotFound if the path doesn’t exist,
Error::NotADirectory if the parent isn’t a directory, and
Error::IsADirectory (POSIX EISDIR) if the target is a directory.
Sourcepub fn apply_create(&self, path: &str, mode: u16) -> Result<u32>
pub fn apply_create(&self, path: &str, mode: u16) -> Result<u32>
Create a new regular file at path with permission bits mode
(e.g. 0o644). Returns the allocated inode number on success.
Semantics:
- Parent must exist and be a directory.
- Refuses if
pathalready exists. - Allocates an inode via
plan_inode_allocation(hints to the parent’s group), marks the bitmap, bumps BGD + SB counters. - Initialises the inode as a regular file with EXTENTS flag and an
empty extent tree (size=0, blocks=0). Timestamps set to
now. - Adds the directory entry into the first parent block with room (linear; htree-extending dirs are a follow-up).
- Not journaled — scratch-image safe, same caveat as other Phase-4 applies.
Sourcepub fn apply_mknod(
&self,
path: &str,
mode: u16,
major: u32,
minor: u32,
) -> Result<u32>
pub fn apply_mknod( &self, path: &str, mode: u16, major: u32, minor: u32, ) -> Result<u32>
Create a special file (FIFO, socket, char device, block device).
mode must include the type bits (S_IFIFO, S_IFSOCK, S_IFCHR,
or S_IFBLK) plus the permission bits. major and minor are the
device numbers (both 0 for FIFOs and sockets). Mirrors POSIX mknod.
Sourcepub fn apply_symlink(&self, target: &str, linkpath: &str) -> Result<u32>
pub fn apply_symlink(&self, target: &str, linkpath: &str) -> Result<u32>
Create a symbolic link at linkpath whose target is target.
Mirrors POSIX symlink(target, linkpath): allocates a fresh inode
with mode S_IFLNK, installs the target bytes, and adds a dir entry
at the link path.
Two storage paths:
- Fast symlink (
target.len() <= 60): target stored inline in the 60-bytei_blockarea; no data-block allocation. - Slow symlink (
61..=255bytes): one filesystem block is allocated and the target is written there, with an EXTENTS i_block pointing at it.
POSIX caps symlink targets at SYMLINK_MAX (255 bytes on Linux +
macOS). Longer returns Error::NameTooLong → ENAMETOOLONG.
Sourcepub fn apply_replace_file_content(&self, path: &str, data: &[u8]) -> Result<u64>
pub fn apply_replace_file_content(&self, path: &str, data: &[u8]) -> Result<u64>
Replace the content of path with data. The file must already
exist. Frees every existing extent, allocates a single contiguous run
of blocks large enough for data, writes the bytes (zero-padding the
tail of the last block), then inserts one extent into the inode.
This is the “Finder just saved a document” path — complete rewrite of a file. Piecewise writes / appends / sparse writes come later.
Journaled, and atomic across the whole replace: freeing the old data, allocating the new run, the bitmap, BGD and superblock updates, the new block contents and the inode all commit as one transaction — as the comment twenty-eight lines into the body already said.
Returns the new file size on success.
Sourcepub fn apply_pwrite(&self, path: &str, offset: u64, data: &[u8]) -> Result<u64>
pub fn apply_pwrite(&self, path: &str, offset: u64, data: &[u8]) -> Result<u64>
Positional write: splice data into the file at byte offset,
allocating new physical blocks for any logical blocks that aren’t
yet mapped (sparse holes, or blocks past EOF). Existing mapped
blocks are read-modify-written for partial overlap; full-block
writes go in fresh.
This is the primitive needed by streaming write paths
(FUSE/WinFsp/FSKit cache-manager dispatches) — apply_replace_file_content
is “save-as”, apply_pwrite is pwrite(2).
Returns the new file size on success.
Allocation behaviour:
- Each unmapped logical run is satisfied by one or more physical
runs. If
plan_block_allocationcan’t find a single contiguous group-local run sized for the whole logical run, the request is halved and retried — each successful sub-run becomes its own extent. True ENOSPC (single-block allocation also fails) surfaces asError::NoSpaceLeftOnDevice. - Extent inserts try the inline-root path first; on
LEAF_FULL_NEEDS_PROMOTIONthey fall back toplan_insert_extent_deep, which promotes the tree to depth ≥ 1 and allocates the additional internal/leaf node blocks via the same buffer-aware allocator. Tail checksums on tree blocks are patched whenmetadata_csumis on.
v1 limitations:
- Extent-tree inodes only. Legacy ext2/3 (direct/indirect blocks)
returns
Error::InvalidArgument. The streaming-copy use case for this path is on freshly-mkfs’d ext4 volumes that always haveEXTENTS_FL. - Pre-existing uninitialised extents (from
fallocate) in the write range: not handled — the unmapped-run walk treats them the same as holes and tries to insert a fresh extent that would overlap, hittingCorruptExtentTree("extent overlaps existing"). Skipping fallocate-then-write, the streaming copy path doesn’t trigger this.
Sourcepub fn apply_mkdir(&self, path: &str, mode: u16) -> Result<u32>
pub fn apply_mkdir(&self, path: &str, mode: u16) -> Result<u32>
Create a subdirectory at path with POSIX mode bits (low 12 bits of
mode). Returns the new directory’s inode number. Steps: allocate
inode (Orlov-hinted) → allocate one data block → seed it with . / ..
→ build dir inode → write inode + data block → add dir entry in parent
→ bump parent’s i_links_count → commit BGD/SB counters.
Not journaled — safe only in scratch-image contexts until transaction wrapping lands.
Sourcepub fn apply_link(&self, src: &str, dst: &str) -> Result<()>
pub fn apply_link(&self, src: &str, dst: &str) -> Result<()>
Create a hard link at dst pointing to the same inode as src.
Semantics:
srcmust exist and must NOT be a directory (POSIX forbids directory hardlinks to avoid reference cycles).dst’s parent must exist and be a directory.dstmust not already exist.- On success the shared inode’s
i_links_countis incremented by 1.
Not journaled — same caveat as other Phase-4 ops.
Sourcepub fn apply_rename(
&self,
src: &str,
dst: &str,
replace_if_exists: bool,
) -> Result<()>
pub fn apply_rename( &self, src: &str, dst: &str, replace_if_exists: bool, ) -> Result<()>
Rename src → dst within the same filesystem.
Semantics:
- Both endpoints are within this mount.
- Works for files and directories.
- Cross-parent moves update the moved dir’s
..entry + bump / decrement both parents’i_links_count. - Refuses to move a directory into its own subtree (cycle check).
- Same source and dest: no-op success.
- When dst already exists:
replace_if_exists = false→ returnsError::AlreadyExists.replace_if_exists = true→ overwrites dst. See “Atomicity” below for exactly how far that holds. Type-compatibility rules (POSIX rename(2)):- file→dir →
Error::IsADirectory - dir→file →
Error::NotADirectory - non-empty-dir overwrite →
Error::DirectoryNotEmpty - src and dst resolve to the same inode (hardlink) → no-op success. Otherwise the previous dst inode’s link count is decremented in the same buffer; if that drops it to zero the inode’s extents and slot are freed in the same atomic commit.
- file→dir →
§Atomicity, and the one place it does not hold
Both paths stage their work into a single [BlockBuffer] and
commit it through the journal, so a crash either applies the
whole rename or none of it.
Except when the destination directory has no room for the new
entry. Then the buffer is committed early and
extend_dir_and_add_entry — which is not journaled — runs
afterwards. That splits the operation in two, and the window
between them is a real one:
- On the overwrite path, the early commit has already removed dst’s directory entry. A crash there leaves dst’s name gone and src still present: the file that was at dst is unreachable, and src has not moved.
- On the no-overwrite path, the early commit is empty, so a crash in the extend leaves the filesystem as it was — but a crash after it leaves both names pointing at src’s inode with a link count of one.
Closing this needs extend_dir_and_add_entry to stage into the
buffer rather than write on its own, which is a change to the
directory-growth path rather than to this function. Until then
the guarantee is: atomic unless the destination directory has
to grow.
Sourcepub fn apply_rmdir(&self, path: &str) -> Result<()>
pub fn apply_rmdir(&self, path: &str) -> Result<()>
Remove an empty directory at path. Requires the target to contain
only . and ... Frees the data block(s) + inode, removes the
entry from the parent, decrements parent’s i_links_count.
Source§impl Filesystem
impl Filesystem
Sourcepub fn audit(
&self,
max_dirs_visited: u32,
max_entries_per_dir: u32,
) -> Result<AuditReport>
pub fn audit( &self, max_dirs_visited: u32, max_entries_per_dir: u32, ) -> Result<AuditReport>
Run an ext4 audit tool-style read-only audit.
Walks from root, counts how many directory entries reference
each inode, and compares that against each inode’s
i_links_count. Returns an AuditReport — empty
anomalies means every invariant we check held.
The pass is bounded: never visits more than
max_dirs_visited directories and never scans more than
max_entries_per_dir entries within a single directory.
Pass u32::MAX for an unbounded pass.
Sourcepub fn audit_repair(
&self,
max_dirs_visited: u32,
max_entries_per_dir: u32,
repair: bool,
) -> Result<AuditReport>
pub fn audit_repair( &self, max_dirs_visited: u32, max_entries_per_dir: u32, repair: bool, ) -> Result<AuditReport>
Audit + repair convenience wrapper. See audit_with_repair
for semantics. No-op on read-only mounts when repair == true
(returns Error::ReadOnly).