use fs_ext4::block_io::FileDevice;
use fs_ext4::jbd2::{self, JBD2_SUPERBLOCK_V1, JBD2_SUPERBLOCK_V2};
use fs_ext4::Filesystem;
use std::sync::Arc;
fn image_path(name: &str) -> String {
format!("{}/test-disks/{}", env!("CARGO_MANIFEST_DIR"), name)
}
fn try_mount(image: &str) -> Option<Filesystem> {
let path = image_path(image);
if !std::path::Path::new(&path).exists() {
eprintln!("skip {image}: not found");
return None;
}
let dev = FileDevice::open(&path).ok()?;
Filesystem::mount(Arc::new(dev)).ok()
}
#[test]
fn journal_sb_round_trips_on_basic_image() {
let Some(fs) = try_mount("ext4-basic.img") else {
return;
};
let sb = jbd2::read_superblock(&fs).expect("read_superblock");
match sb {
Some(jsb) => {
assert!(
matches!(jsb.block_type, JBD2_SUPERBLOCK_V1 | JBD2_SUPERBLOCK_V2),
"bad block_type {}",
jsb.block_type
);
assert_eq!(
jsb.block_size,
fs.sb.block_size(),
"journal block_size {} != fs block_size {}",
jsb.block_size,
fs.sb.block_size()
);
assert!(jsb.max_len > 0, "max_len == 0");
assert!(jsb.first >= 1, "first block < 1: {}", jsb.first);
assert_eq!(jsb.errno, 0, "journal errno = {}", jsb.errno);
assert!(
jsb.is_clean(),
"expected clean journal (start=0), got start={}",
jsb.start
);
}
None => {
assert_eq!(fs.sb.journal_inode, 0, "journal_inode != 0 but got None");
}
}
}
#[test]
fn journal_sb_on_csum_seed_image() {
let Some(fs) = try_mount("ext4-csum-seed.img") else {
return;
};
let Ok(Some(jsb)) = jbd2::read_superblock(&fs) else {
return;
};
assert!(matches!(
jsb.block_type,
JBD2_SUPERBLOCK_V1 | JBD2_SUPERBLOCK_V2
));
assert_eq!(jsb.block_size, fs.sb.block_size());
assert!(jsb.is_clean());
}
#[test]
fn journal_sb_on_htree_image() {
let Some(fs) = try_mount("ext4-htree.img") else {
return;
};
let Ok(Some(jsb)) = jbd2::read_superblock(&fs) else {
return;
};
assert!(matches!(
jsb.block_type,
JBD2_SUPERBLOCK_V1 | JBD2_SUPERBLOCK_V2
));
assert!(jsb.max_len > 0);
assert!(jsb.is_clean());
}