#![forbid(unsafe_code)]
use crate::error::Result;
use crate::inode::InodeReader;
use crate::ondisk::superblock::Superblock;
use std::io::{Read, Seek};
#[derive(Debug, Clone)]
pub struct SuperblockComparison {
pub group: u32,
pub block: u64,
pub matches_primary: bool,
pub differences: Vec<String>,
}
fn backup_groups(group_count: u32) -> Vec<u32> {
let mut groups = vec![0, 1]; for base in [3u32, 5, 7] {
let mut power = base;
while power < group_count {
if !groups.contains(&power) {
groups.push(power);
}
power = match power.checked_mul(base) {
Some(p) => p,
None => break,
};
}
}
groups.sort_unstable();
groups.retain(|&g| g < group_count);
groups
}
fn compare_superblocks(primary: &Superblock, backup: &Superblock) -> Vec<String> {
let mut diffs = Vec::new();
if primary.magic != backup.magic {
diffs.push("magic".to_string());
}
if primary.block_size != backup.block_size {
diffs.push("block_size".to_string());
}
if primary.blocks_count != backup.blocks_count {
diffs.push("blocks_count".to_string());
}
if primary.inodes_count != backup.inodes_count {
diffs.push("inodes_count".to_string());
}
if primary.blocks_per_group != backup.blocks_per_group {
diffs.push("blocks_per_group".to_string());
}
if primary.inodes_per_group != backup.inodes_per_group {
diffs.push("inodes_per_group".to_string());
}
if primary.uuid != backup.uuid {
diffs.push("uuid".to_string());
}
if primary.inode_size != backup.inode_size {
diffs.push("inode_size".to_string());
}
if primary.feature_compat != backup.feature_compat {
diffs.push("feature_compat".to_string());
}
if primary.feature_incompat != backup.feature_incompat {
diffs.push("feature_incompat".to_string());
}
if primary.feature_ro_compat != backup.feature_ro_compat {
diffs.push("feature_ro_compat".to_string());
}
diffs
}
pub fn verify_superblock_backups<R: Read + Seek>(
reader: &mut InodeReader<R>,
) -> Result<Vec<SuperblockComparison>> {
let primary = reader.block_reader().superblock().clone();
let group_count = reader.block_reader().group_count();
let block_size = u64::from(primary.block_size);
let blocks_per_group = u64::from(primary.blocks_per_group);
let groups = backup_groups(group_count);
let mut results = Vec::new();
for &group in &groups {
if group == 0 {
continue;
}
let block = u64::from(group) * blocks_per_group;
let sb_byte_offset = if block_size >= 2048 {
block * block_size
} else {
block * block_size + 1024
};
let buf = match reader.block_reader_mut().read_bytes(sb_byte_offset, 1024) {
Ok(b) => b,
Err(_) => continue,
};
let backup_sb = if let Ok(sb) = Superblock::parse(&buf) {
sb
} else {
results.push(SuperblockComparison {
group,
block,
matches_primary: false,
differences: vec!["unparseable".to_string()],
});
continue;
};
let diffs = compare_superblocks(&primary, &backup_sb);
results.push(SuperblockComparison {
group,
block,
matches_primary: diffs.is_empty(),
differences: diffs,
});
}
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::BlockReader;
use std::io::Cursor;
fn open_forensic() -> Option<InodeReader<Cursor<Vec<u8>>>> {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data/forensic.img");
let data = std::fs::read(path).ok()?;
let br = BlockReader::open(Cursor::new(data)).ok()?;
Some(InodeReader::new(br))
}
#[test]
fn backup_groups_small_image() {
let groups = backup_groups(1);
assert_eq!(groups, vec![0]);
}
#[test]
fn backup_groups_medium_image() {
let groups = backup_groups(10);
assert!(groups.contains(&0));
assert!(groups.contains(&1));
assert!(groups.contains(&3));
assert!(groups.contains(&5));
assert!(groups.contains(&7));
assert!(groups.contains(&9)); }
#[test]
fn backup_groups_large_image() {
let groups = backup_groups(200);
assert!(groups.contains(&0));
assert!(groups.contains(&1));
assert!(groups.contains(&25)); assert!(groups.contains(&27)); assert!(groups.contains(&49)); assert!(groups.contains(&125)); assert!(!groups.contains(&200)); }
#[test]
fn verify_backups_on_forensic_img() {
let mut reader = if let Some(r) = open_forensic() {
r
} else {
eprintln!("skip");
return;
};
let results = verify_superblock_backups(&mut reader).unwrap();
eprintln!("Backup verification results: {} entries", results.len());
for r in &results {
eprintln!(
" group {}: matches={}, diffs={:?}",
r.group, r.matches_primary, r.differences
);
}
}
#[test]
fn compare_identical_superblocks() {
let mut buf = vec![0u8; 1024];
buf[0x38..0x3A].copy_from_slice(&0xEF53u16.to_le_bytes());
buf[0x18..0x1C].copy_from_slice(&2u32.to_le_bytes()); buf[0x28..0x2C].copy_from_slice(&64u32.to_le_bytes()); buf[0x04..0x08].copy_from_slice(&100u32.to_le_bytes()); buf[0x58..0x5A].copy_from_slice(&256u16.to_le_bytes()); buf[0x4C..0x50].copy_from_slice(&1u32.to_le_bytes()); let sb = Superblock::parse(&buf).unwrap();
let diffs = compare_superblocks(&sb, &sb);
assert!(
diffs.is_empty(),
"identical superblocks should have no differences"
);
}
}