use crate::bgd;
use crate::dir::{self, DirBlockIter, DirEntryType};
use crate::error::{Error, Result};
use crate::extent;
use crate::features;
use crate::fs::{BlockBuffer, Filesystem};
use crate::inode::Inode;
use crate::superblock::Superblock;
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Anomaly {
LinkCountTooLow {
ino: u32,
stored: u16,
observed: u32,
},
LinkCountTooHigh {
ino: u32,
stored: u16,
observed: u32,
},
DanglingEntry {
parent_ino: u32,
child_ino: u32,
observed: u32,
},
WrongDotDot {
dir_ino: u32,
claims: u32,
actual_parent: u32,
},
BogusEntry {
parent_ino: u32,
child_ino: u32,
name: Vec<u8>,
},
BlockGroupFreeCountDrift {
group_index: u32,
stored_blocks: u32,
observed_blocks: u32,
stored_inodes: u32,
observed_inodes: u32,
},
SuperblockFreeCountDrift {
stored_blocks: u64,
observed_blocks: u64,
stored_inodes: u32,
observed_inodes: u32,
},
DuplicateDirentForDirInode {
ino: u32,
dirents: Vec<(u32, String)>,
},
}
#[derive(Debug, Clone, Default)]
pub struct AuditReport {
pub anomalies: Vec<Anomaly>,
pub inodes_visited: u32,
pub entries_scanned: u64,
pub directories_scanned: u32,
pub anomalies_count: u64,
pub initial_anomalies_count: u64,
pub repaired_count: u64,
}
impl AuditReport {
pub fn is_clean(&self) -> bool {
self.anomalies_count == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum FsckPhase {
Superblock = 0,
Journal = 1,
Directory = 2,
Inodes = 3,
Finalize = 4,
}
impl FsckPhase {
pub fn name(self) -> &'static str {
match self {
FsckPhase::Superblock => "superblock",
FsckPhase::Journal => "journal",
FsckPhase::Directory => "directory",
FsckPhase::Inodes => "inodes",
FsckPhase::Finalize => "finalize",
}
}
}
pub fn audit(
fs: &Filesystem,
max_dirs_visited: u32,
max_entries_per_dir: u32,
) -> Result<AuditReport> {
let mut report = AuditReport::default();
let mut collected: Vec<Anomaly> = Vec::new();
audit_inner(
fs,
max_dirs_visited,
max_entries_per_dir,
&mut |_, _, _| {},
&mut |a| collected.push(a.clone()),
&mut report,
)?;
report.anomalies = collected;
Ok(report)
}
pub fn audit_with_callbacks<P, F>(
fs: &Filesystem,
max_dirs_visited: u32,
max_entries_per_dir: u32,
mut on_progress: P,
mut on_finding: F,
) -> Result<AuditReport>
where
P: FnMut(FsckPhase, u64, u64),
F: FnMut(&Anomaly),
{
let mut report = AuditReport::default();
on_progress(FsckPhase::Superblock, 0, 1);
on_progress(FsckPhase::Superblock, 1, 1);
audit_inner(
fs,
max_dirs_visited,
max_entries_per_dir,
&mut on_progress,
&mut on_finding,
&mut report,
)?;
Ok(report)
}
fn audit_inner(
fs: &Filesystem,
max_dirs_visited: u32,
max_entries_per_dir: u32,
on_progress: &mut dyn FnMut(FsckPhase, u64, u64),
on_finding: &mut dyn FnMut(&Anomaly),
report: &mut AuditReport,
) -> Result<()> {
let mut observed: HashMap<u32, u32> = HashMap::new();
let mut parent_claim: HashMap<u32, u32> = HashMap::new();
let mut actual_parent: HashMap<u32, u32> = HashMap::new();
let mut incomplete_dirs: std::collections::HashSet<u32> = std::collections::HashSet::new();
let mut dirent_index: HashMap<u32, Vec<(u32, Vec<u8>)>> = HashMap::new();
let mut work: Vec<(u32, u32, Vec<u8>)> = Vec::new();
work.push((
crate::path::EXT4_ROOT_INODE,
crate::path::EXT4_ROOT_INODE,
Vec::new(),
));
let mut visited: std::collections::HashSet<u32> = std::collections::HashSet::new();
let has_filetype = fs.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
let block_size = fs.sb.block_size();
on_progress(FsckPhase::Directory, 0, work.len() as u64);
while let Some((dir_ino, parent_ino, dirent_name)) = work.pop() {
if report.directories_scanned >= max_dirs_visited {
incomplete_dirs.insert(dir_ino);
break;
}
if !visited.insert(dir_ino) {
continue;
}
actual_parent.entry(dir_ino).or_insert(parent_ino);
report.directories_scanned += 1;
let (inode, _raw) = match fs.read_inode_verified(dir_ino) {
Ok(p) => p,
Err(_) => {
incomplete_dirs.insert(dir_ino);
emit_dir_progress(on_progress, report.directories_scanned, work.len());
continue;
}
};
if !inode.is_dir() {
let a = Anomaly::BogusEntry {
parent_ino,
child_ino: dir_ino,
name: dirent_name.clone(),
};
on_finding(&a);
report.anomalies_count += 1;
emit_dir_progress(on_progress, report.directories_scanned, work.len());
continue;
}
if inode.has_inline_data() {
incomplete_dirs.insert(dir_ino);
emit_dir_progress(on_progress, report.directories_scanned, work.len());
continue;
}
let entries = match collect_dir_entries(fs, &inode, has_filetype, block_size) {
Ok(e) => e,
Err(_) => {
incomplete_dirs.insert(dir_ino);
emit_dir_progress(on_progress, report.directories_scanned, work.len());
continue;
}
};
let mut truncated = false;
for (n_scanned, entry) in (0u32..).zip(entries) {
if n_scanned >= max_entries_per_dir {
truncated = true;
break;
}
report.entries_scanned += 1;
if entry.name == b"." {
*observed.entry(dir_ino).or_insert(0) += 1;
continue;
}
if entry.name == b".." {
parent_claim.insert(dir_ino, entry.inode);
*observed.entry(entry.inode).or_insert(0) += 1;
continue;
}
*observed.entry(entry.inode).or_insert(0) += 1;
dirent_index
.entry(entry.inode)
.or_default()
.push((dir_ino, entry.name.clone()));
if matches!(entry.file_type, DirEntryType::Directory) {
work.push((entry.inode, dir_ino, entry.name.clone()));
}
}
if truncated {
incomplete_dirs.insert(dir_ino);
}
emit_dir_progress(on_progress, report.directories_scanned, work.len());
}
report.inodes_visited = observed.len() as u32;
let inodes_total = observed.len() as u64;
on_progress(FsckPhase::Inodes, 0, inodes_total.max(1));
let have_incomplete = !incomplete_dirs.is_empty();
let mut inodes_done: u64 = 0;
let mut last_tick = Instant::now();
let tick = Duration::from_millis(500);
for (&ino, &count) in observed.iter() {
match fs.read_inode_verified(ino) {
Ok((inode, _)) => {
let stored = inode.links_count;
if stored == 0 {
let a = Anomaly::DanglingEntry {
parent_ino: 0,
child_ino: ino,
observed: count,
};
on_finding(&a);
report.anomalies_count += 1;
continue;
}
if (stored as u32) < count {
let a = Anomaly::LinkCountTooLow {
ino,
stored,
observed: count,
};
on_finding(&a);
report.anomalies_count += 1;
}
if (stored as u32) > count && !have_incomplete {
let a = Anomaly::LinkCountTooHigh {
ino,
stored,
observed: count,
};
on_finding(&a);
report.anomalies_count += 1;
}
}
Err(_) => {
let a = Anomaly::DanglingEntry {
parent_ino: 0,
child_ino: ino,
observed: 0,
};
on_finding(&a);
report.anomalies_count += 1;
}
}
inodes_done += 1;
if last_tick.elapsed() >= tick {
on_progress(FsckPhase::Inodes, inodes_done, inodes_total.max(1));
last_tick = Instant::now();
}
}
let mut dup_keys: Vec<u32> = dirent_index
.iter()
.filter_map(|(ino, refs)| if refs.len() > 1 { Some(*ino) } else { None })
.collect();
dup_keys.sort_unstable();
for ino in dup_keys {
let is_dir = match fs.read_inode_verified(ino) {
Ok((inode, _)) => inode.is_dir(),
Err(_) => continue,
};
if !is_dir {
continue;
}
let mut refs = dirent_index.get(&ino).cloned().unwrap_or_default();
refs.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
let dirents: Vec<(u32, String)> = refs
.into_iter()
.map(|(p, n)| (p, String::from_utf8_lossy(&n).into_owned()))
.collect();
let a = Anomaly::DuplicateDirentForDirInode { ino, dirents };
on_finding(&a);
report.anomalies_count += 1;
}
for (&dir_ino, &claimed) in parent_claim.iter() {
let truth = if dir_ino == crate::path::EXT4_ROOT_INODE {
crate::path::EXT4_ROOT_INODE
} else {
match actual_parent.get(&dir_ino) {
Some(&p) => p,
None => continue,
}
};
if claimed != truth {
let a = Anomaly::WrongDotDot {
dir_ino,
claims: claimed,
actual_parent: truth,
};
on_finding(&a);
report.anomalies_count += 1;
}
}
on_progress(FsckPhase::Inodes, inodes_total.max(1), inodes_total.max(1));
audit_free_counts(fs, on_finding, report)?;
on_progress(FsckPhase::Finalize, 0, 1);
on_progress(FsckPhase::Finalize, 1, 1);
Ok(())
}
fn audit_free_counts(
fs: &Filesystem,
on_finding: &mut dyn FnMut(&Anomaly),
report: &mut AuditReport,
) -> Result<()> {
let bpg = fs.sb.blocks_per_group as u64;
let ipg = fs.sb.inodes_per_group as u64;
let total_blocks = fs.sb.blocks_count;
let first_data = fs.sb.first_data_block as u64;
let live_groups = bgd::read_all(fs.dev.as_ref(), &fs.sb, &fs.csum)?;
let live_sb = Superblock::read(fs.dev.as_ref())?;
let mut sum_free_blocks: u64 = 0;
let mut sum_free_inodes: u64 = 0;
for (gi, bg) in live_groups.iter().enumerate() {
let group_first_block = first_data + gi as u64 * bpg;
let group_block_count = std::cmp::min(bpg, total_blocks.saturating_sub(group_first_block));
let block_bitmap = fs.read_block(bg.block_bitmap)?;
let observed_blocks = count_zero_bits_le(&block_bitmap, group_block_count as u32);
let inode_bitmap = fs.read_block(bg.inode_bitmap)?;
let observed_inodes = count_zero_bits_le(&inode_bitmap, ipg as u32);
sum_free_blocks += observed_blocks as u64;
sum_free_inodes += observed_inodes as u64;
if observed_blocks != bg.free_blocks_count || observed_inodes != bg.free_inodes_count {
let a = Anomaly::BlockGroupFreeCountDrift {
group_index: gi as u32,
stored_blocks: bg.free_blocks_count,
observed_blocks,
stored_inodes: bg.free_inodes_count,
observed_inodes,
};
on_finding(&a);
report.anomalies_count += 1;
}
}
if sum_free_blocks != live_sb.free_blocks_count
|| (sum_free_inodes as u32) != live_sb.free_inodes_count
{
let a = Anomaly::SuperblockFreeCountDrift {
stored_blocks: live_sb.free_blocks_count,
observed_blocks: sum_free_blocks,
stored_inodes: live_sb.free_inodes_count,
observed_inodes: sum_free_inodes as u32,
};
on_finding(&a);
report.anomalies_count += 1;
}
Ok(())
}
fn count_zero_bits_le(buf: &[u8], total_bits: u32) -> u32 {
let full_bytes = (total_bits / 8) as usize;
let mut free: u32 = 0;
for i in 0..full_bytes {
if i >= buf.len() {
break;
}
free += buf[i].count_zeros();
}
let leftover_bits = total_bits % 8;
if leftover_bits > 0 && full_bytes < buf.len() {
let last = buf[full_bytes];
let mask = (1u8 << leftover_bits) - 1;
let ones_in_used_bits = (last & mask).count_ones();
free += leftover_bits - ones_in_used_bits;
}
free
}
fn emit_dir_progress(
on_progress: &mut dyn FnMut(FsckPhase, u64, u64),
scanned: u32,
queue_len: usize,
) {
let done = scanned as u64;
let total = done + queue_len as u64;
on_progress(FsckPhase::Directory, done, total);
}
fn collect_dir_entries(
fs: &Filesystem,
inode: &Inode,
has_filetype: bool,
block_size: u32,
) -> Result<Vec<crate::dir::DirEntry>> {
let mut entries = Vec::new();
if inode.has_inline_data() {
for entry in DirBlockIter::new(&inode.block, has_filetype) {
entries.push(entry?);
}
return Ok(entries);
}
if !inode.has_extents() {
return Err(Error::Corrupt(
"legacy non-extent dirs not supported by audit",
));
}
let total_blocks = inode.size.div_ceil(block_size as u64);
let mut buf = vec![0u8; block_size as usize];
for logical in 0..total_blocks {
let Some(phys) = extent::map_logical(&inode.block, fs.dev.as_ref(), block_size, logical)?
else {
continue;
};
let offset = phys
.checked_mul(block_size as u64)
.ok_or(Error::Corrupt("audit: dir block offset overflow"))?;
fs.dev.read_at(offset, &mut buf)?;
for entry in DirBlockIter::new(&buf, has_filetype) {
match entry {
Ok(e) => entries.push(e),
Err(_) if logical == 0 => continue,
Err(e) => return Err(e),
}
}
}
Ok(entries)
}
pub fn audit_with_repair<P, F>(
fs: &Filesystem,
max_dirs_visited: u32,
max_entries_per_dir: u32,
mut on_progress: P,
mut on_finding: F,
repair: bool,
) -> Result<AuditReport>
where
P: FnMut(FsckPhase, u64, u64),
F: FnMut(&Anomaly),
{
if repair && !fs.dev.is_writable() {
return Err(Error::ReadOnly);
}
let mut report = AuditReport::default();
on_progress(FsckPhase::Superblock, 0, 1);
on_progress(FsckPhase::Superblock, 1, 1);
let mut collected: Vec<Anomaly> = Vec::new();
audit_inner(
fs,
max_dirs_visited,
max_entries_per_dir,
&mut on_progress,
&mut |a| {
on_finding(a);
collected.push(a.clone());
},
&mut report,
)?;
report.initial_anomalies_count = report.anomalies_count;
if !repair {
report.anomalies = collected;
return Ok(report);
}
let surviving_parent_after_dedup: HashMap<u32, u32> = collected
.iter()
.filter_map(|a| match a {
Anomaly::DuplicateDirentForDirInode { ino, dirents } if !dirents.is_empty() => {
Some((*ino, dirents[0].0))
}
_ => None,
})
.collect();
for finding in &collected {
match finding {
Anomaly::DuplicateDirentForDirInode { ino, dirents } => {
repair_duplicate_dir_inode(fs, *ino, dirents, &mut report)?;
}
Anomaly::LinkCountTooLow {
ino,
stored: _,
observed,
}
| Anomaly::LinkCountTooHigh {
ino,
stored: _,
observed,
} => {
repair_link_count(fs, *ino, *observed, &mut report)?;
}
Anomaly::WrongDotDot {
dir_ino,
claims: _,
actual_parent,
} => {
let target_parent = surviving_parent_after_dedup
.get(dir_ino)
.copied()
.unwrap_or(*actual_parent);
repair_wrong_dotdot(fs, *dir_ino, target_parent, &mut report)?;
}
Anomaly::BogusEntry {
parent_ino,
child_ino,
name,
} => {
repair_bogus_entry(fs, *parent_ino, *child_ino, name, &mut report)?;
}
Anomaly::DanglingEntry {
parent_ino: _,
child_ino,
observed,
} => {
repair_link_count(fs, *child_ino, *observed, &mut report)?;
}
Anomaly::BlockGroupFreeCountDrift {
group_index,
stored_blocks,
observed_blocks,
stored_inodes,
observed_inodes,
} => {
repair_block_group_free_counts(
fs,
*group_index,
*stored_blocks,
*observed_blocks,
*stored_inodes,
*observed_inodes,
&mut report,
)?;
}
Anomaly::SuperblockFreeCountDrift {
stored_blocks,
observed_blocks,
stored_inodes,
observed_inodes,
} => {
repair_superblock_free_counts(
fs,
*stored_blocks,
*observed_blocks,
*stored_inodes,
*observed_inodes,
&mut report,
)?;
}
}
}
let mut post_report = AuditReport::default();
let mut remaining: Vec<Anomaly> = Vec::new();
audit_inner(
fs,
max_dirs_visited,
max_entries_per_dir,
&mut on_progress,
&mut |a| {
remaining.push(a.clone());
},
&mut post_report,
)?;
report.anomalies = remaining;
report.anomalies_count = post_report.anomalies_count;
Ok(report)
}
fn repair_duplicate_dir_inode(
fs: &Filesystem,
ino: u32,
dirents: &[(u32, String)],
report: &mut AuditReport,
) -> Result<()> {
if dirents.len() < 2 {
return Ok(());
}
let has_ft = fs.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
let bs = fs.sb.block_size();
let mut any_removed = false;
for (parent_ino, name) in dirents.iter().skip(1) {
let (parent_inode, _parent_raw) = fs.read_inode_verified(*parent_ino)?;
if !parent_inode.is_dir() {
continue;
}
let mut buf = BlockBuffer::new(bs);
let parent_blocks = parent_inode.size.div_ceil(bs as u64);
let mut removed = false;
for logical in 0..parent_blocks {
let Some(phys) = fs.map_inode_logical(&parent_inode, logical)? else {
continue;
};
let block = buf.get_mut(fs, phys)?;
let reserved_tail = if fs.csum.enabled && dir::has_csum_tail(block) {
12
} else {
0
};
if dir::remove_entry_from_block(block, name.as_bytes(), has_ft, reserved_tail)? {
if fs.csum.enabled && reserved_tail == 12 {
let end = block.len();
let mut c =
crate::checksum::linux_crc32c(fs.csum.seed, &parent_ino.to_le_bytes());
c = crate::checksum::linux_crc32c(c, &parent_inode.generation.to_le_bytes());
c = crate::checksum::linux_crc32c(c, &block[..end - 12]);
block[end - 4..end].copy_from_slice(&c.to_le_bytes());
}
removed = true;
break;
}
}
if !removed {
continue;
}
fs.commit_block_buffer(buf)?;
any_removed = true;
}
if any_removed {
report.repaired_count += 1;
}
let (kept_inode, mut kept_raw) = fs.read_inode_verified(ino)?;
if !kept_inode.is_dir() {
return Ok(());
}
let subdir_count = count_subdirs(fs, &kept_inode, has_ft, bs)?;
let new_nlink: u16 = 2u16.saturating_add(subdir_count.min(u16::MAX as u32 - 2) as u16);
kept_raw[0x1A..0x1C].copy_from_slice(&new_nlink.to_le_bytes());
finalize_and_commit_inode(fs, ino, kept_inode.generation, &mut kept_raw)?;
Ok(())
}
fn count_subdirs(
fs: &Filesystem,
dir_inode: &Inode,
has_filetype: bool,
block_size: u32,
) -> Result<u32> {
let entries = collect_dir_entries(fs, dir_inode, has_filetype, block_size)?;
let mut n = 0u32;
for e in entries {
if e.name == b"." || e.name == b".." {
continue;
}
if matches!(e.file_type, DirEntryType::Directory) {
if let Ok((child_inode, _)) = fs.read_inode_verified(e.inode) {
if child_inode.is_dir() {
n = n.saturating_add(1);
}
}
}
}
Ok(n)
}
fn repair_link_count(
fs: &Filesystem,
ino: u32,
observed: u32,
report: &mut AuditReport,
) -> Result<()> {
if observed == 0 || observed > u16::MAX as u32 {
return Ok(());
}
let (inode, mut raw) = fs.read_inode_verified(ino)?;
raw[0x1A..0x1C].copy_from_slice(&(observed as u16).to_le_bytes());
finalize_and_commit_inode(fs, ino, inode.generation, &mut raw)?;
report.repaired_count += 1;
Ok(())
}
fn repair_wrong_dotdot(
fs: &Filesystem,
dir_ino: u32,
actual_parent: u32,
report: &mut AuditReport,
) -> Result<()> {
let (dir_inode, _raw) = fs.read_inode_verified(dir_ino)?;
if !dir_inode.is_dir() {
return Ok(());
}
let bs = fs.sb.block_size();
let Some(phys) = fs.map_inode_logical(&dir_inode, 0)? else {
return Ok(());
};
let mut buf = BlockBuffer::new(bs);
let block = buf.get_mut(fs, phys)?;
let reserved_tail = if fs.csum.enabled && dir::has_csum_tail(block) {
12
} else {
0
};
let usable_end = block.len().saturating_sub(reserved_tail);
let mut off = 0usize;
let mut found = false;
while off + 8 <= usable_end {
let rec_len = u16::from_le_bytes([block[off + 4], block[off + 5]]) as usize;
if rec_len == 0 || off + rec_len > usable_end {
break;
}
let name_len = block[off + 6] as usize;
let name_start = off + 8;
let name_end = name_start + name_len;
if name_end <= off + rec_len && &block[name_start..name_end] == b".." {
block[off..off + 4].copy_from_slice(&actual_parent.to_le_bytes());
found = true;
break;
}
off += rec_len;
}
if !found {
return Ok(());
}
if fs.csum.enabled && reserved_tail == 12 {
let end = block.len();
let mut c = crate::checksum::linux_crc32c(fs.csum.seed, &dir_ino.to_le_bytes());
c = crate::checksum::linux_crc32c(c, &dir_inode.generation.to_le_bytes());
c = crate::checksum::linux_crc32c(c, &block[..end - 12]);
block[end - 4..end].copy_from_slice(&c.to_le_bytes());
}
fs.commit_block_buffer(buf)?;
report.repaired_count += 1;
Ok(())
}
fn repair_bogus_entry(
fs: &Filesystem,
parent_ino: u32,
child_ino: u32,
name: &[u8],
report: &mut AuditReport,
) -> Result<()> {
let has_ft = fs.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
if !has_ft {
return Ok(());
}
if name.is_empty() {
return Ok(());
}
let (parent_inode, _parent_raw) = fs.read_inode_verified(parent_ino)?;
if !parent_inode.is_dir() {
return Ok(());
}
let child_filetype: DirEntryType = match fs.read_inode_verified(child_ino) {
Ok((child_inode, _)) => {
let mode_bits = child_inode.mode & crate::inode::S_IFMT;
match mode_bits {
crate::inode::S_IFREG => DirEntryType::RegFile,
crate::inode::S_IFDIR => return Ok(()),
crate::inode::S_IFLNK => DirEntryType::Symlink,
crate::inode::S_IFBLK => DirEntryType::BlockDev,
crate::inode::S_IFCHR => DirEntryType::CharDev,
crate::inode::S_IFIFO => DirEntryType::Fifo,
crate::inode::S_IFSOCK => DirEntryType::Socket,
_ => return Ok(()),
}
}
Err(_) => return Ok(()),
};
let bs = fs.sb.block_size();
let parent_blocks = parent_inode.size.div_ceil(bs as u64);
let mut buf = BlockBuffer::new(bs);
let mut found = false;
for logical in 0..parent_blocks {
let Some(phys) = fs.map_inode_logical(&parent_inode, logical)? else {
continue;
};
let block = buf.get_mut(fs, phys)?;
let reserved_tail = if fs.csum.enabled && dir::has_csum_tail(block) {
12
} else {
0
};
let usable_end = block.len().saturating_sub(reserved_tail);
let mut off = 0usize;
let mut hit_off: Option<usize> = None;
while off + 8 <= usable_end {
let cur_inode =
u32::from_le_bytes([block[off], block[off + 1], block[off + 2], block[off + 3]]);
let rec_len = u16::from_le_bytes([block[off + 4], block[off + 5]]) as usize;
if rec_len == 0 || off + rec_len > usable_end {
break;
}
let name_len = block[off + 6] as usize;
if cur_inode == child_ino
&& 8 + name_len <= rec_len
&& off + 8 + name_len <= usable_end
&& &block[off + 8..off + 8 + name_len] == name
{
hit_off = Some(off);
break;
}
off += rec_len;
}
if let Some(off) = hit_off {
block[off + 7] = child_filetype as u8;
if fs.csum.enabled && reserved_tail == 12 {
let end = block.len();
let mut c = crate::checksum::linux_crc32c(fs.csum.seed, &parent_ino.to_le_bytes());
c = crate::checksum::linux_crc32c(c, &parent_inode.generation.to_le_bytes());
c = crate::checksum::linux_crc32c(c, &block[..end - 12]);
block[end - 4..end].copy_from_slice(&c.to_le_bytes());
}
found = true;
break;
}
}
if found {
fs.commit_block_buffer(buf)?;
report.repaired_count += 1;
}
Ok(())
}
fn repair_block_group_free_counts(
fs: &Filesystem,
group_index: u32,
stored_blocks: u32,
observed_blocks: u32,
stored_inodes: u32,
observed_inodes: u32,
report: &mut AuditReport,
) -> Result<()> {
let block_delta = (observed_blocks as i64) - (stored_blocks as i64);
let inode_delta = (observed_inodes as i64) - (stored_inodes as i64);
if block_delta == 0 && inode_delta == 0 {
return Ok(());
}
if block_delta < i32::MIN as i64
|| block_delta > i32::MAX as i64
|| inode_delta < i32::MIN as i64
|| inode_delta > i32::MAX as i64
{
return Ok(());
}
fs.patch_bgd_counters(
group_index as usize,
block_delta as i32,
inode_delta as i32,
0,
)?;
report.repaired_count += 1;
Ok(())
}
fn repair_superblock_free_counts(
fs: &Filesystem,
stored_blocks: u64,
observed_blocks: u64,
stored_inodes: u32,
observed_inodes: u32,
report: &mut AuditReport,
) -> Result<()> {
let block_delta = (observed_blocks as i64) - (stored_blocks as i64);
let inode_delta = (observed_inodes as i64) - (stored_inodes as i64);
if block_delta == 0 && inode_delta == 0 {
return Ok(());
}
if inode_delta < i32::MIN as i64 || inode_delta > i32::MAX as i64 {
return Ok(());
}
fs.patch_sb_counters(block_delta, inode_delta as i32)?;
report.repaired_count += 1;
Ok(())
}
fn finalize_and_commit_inode(
fs: &Filesystem,
ino: u32,
generation: u32,
raw: &mut [u8],
) -> Result<()> {
if fs.csum.enabled {
if let Some((lo, hi)) = fs.csum.compute_inode_checksum(ino, generation, raw) {
raw[0x7C..0x7E].copy_from_slice(&lo.to_le_bytes());
if raw.len() >= 0x84 {
raw[0x82..0x84].copy_from_slice(&hi.to_le_bytes());
}
}
}
let mut buf = BlockBuffer::new(fs.sb.block_size());
fs.buffer_write_inode(&mut buf, ino, raw)?;
fs.commit_block_buffer(buf)
}
impl Filesystem {
pub fn audit(&self, max_dirs_visited: u32, max_entries_per_dir: u32) -> Result<AuditReport> {
audit(self, max_dirs_visited, max_entries_per_dir)
}
pub fn audit_repair(
&self,
max_dirs_visited: u32,
max_entries_per_dir: u32,
repair: bool,
) -> Result<AuditReport> {
audit_with_repair(
self,
max_dirs_visited,
max_entries_per_dir,
|_, _, _| {},
|_| {},
repair,
)
}
}