use fs_ext4::block_io::FileDevice;
use fs_ext4::path as path_mod;
use fs_ext4::Filesystem;
use std::fs;
use std::sync::Arc;
#[track_caller]
fn image_path(name: &str) -> String {
fs_ext4_test_support::fixture(env!("CARGO_MANIFEST_DIR"), name)
}
#[track_caller]
fn copy_to_tmp(name: &str, tag: &str) -> String {
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let src = image_path(name);
let dst =
fs_ext4_test_support::temp_path!("fs_ext4_alloc_ctr_{}_{tag}_{n}.img", std::process::id());
fs::copy(&src, &dst).unwrap_or_else(|e| panic!("copy {src} -> {dst}: {e}"));
dst
}
fn resolve(fs: &Filesystem, path: &str) -> u32 {
let mut reader = |ino: u32| fs.read_inode_verified(ino).map(|(i, _)| i);
path_mod::lookup(fs.dev.as_ref(), &fs.sb, &mut reader, path).expect("resolve")
}
fn sb_free_blocks(path: &str) -> u64 {
let dev = FileDevice::open(path).expect("open ro");
let fs = Filesystem::mount(Arc::new(dev)).expect("mount");
fs.sb.free_blocks_count
}
fn bgd_free_blocks(path: &str, gi: usize) -> u32 {
let dev = FileDevice::open(path).expect("open ro");
let fs = Filesystem::mount(Arc::new(dev)).expect("mount");
fs.groups[gi].free_blocks_count
}
#[test]
fn truncate_shrink_updates_sb_and_bgd_free_blocks() {
let path = copy_to_tmp("ext4-basic.img", "trunc_sb_bgd");
let sb_before = sb_free_blocks(&path);
let bgd0_before = bgd_free_blocks(&path, 0);
{
let dev = FileDevice::open_rw(&path).expect("open rw");
let fs = Filesystem::mount(Arc::new(dev)).expect("mount");
let ino = resolve(&fs, "/test.txt");
fs.apply_truncate_shrink(ino, 0).expect("truncate");
}
let sb_after = sb_free_blocks(&path);
let bgd0_after = bgd_free_blocks(&path, 0);
assert!(
sb_after > sb_before,
"SB free_blocks_count did not increase: {sb_before} -> {sb_after} \
(Phase 1.1 regression: apply_truncate_shrink skipped patch_sb_counters)"
);
assert!(
bgd0_after > bgd0_before,
"BGD[0] free_blocks_count did not increase: {bgd0_before} -> {bgd0_after} \
(Phase 1.1 regression: apply_truncate_shrink skipped patch_bgd_counters)"
);
let sb_delta = sb_after - sb_before;
let bgd_delta = (bgd0_after - bgd0_before) as u64;
assert_eq!(
sb_delta, bgd_delta,
"SB delta ({sb_delta}) != BGD delta ({bgd_delta}); counters drifted"
);
fs::remove_file(path).ok();
}
#[test]
fn unlink_round_trip_keeps_sb_and_bgd_in_sync() {
let path = copy_to_tmp("ext4-basic.img", "unlink_sb_bgd");
let sb_before = sb_free_blocks(&path);
let bgd0_before = bgd_free_blocks(&path, 0);
{
let dev = FileDevice::open_rw(&path).expect("open rw");
let fs = Filesystem::mount(Arc::new(dev)).expect("mount");
fs.apply_unlink("/test.txt").expect("unlink");
}
let sb_after = sb_free_blocks(&path);
let bgd0_after = bgd_free_blocks(&path, 0);
let sb_delta = sb_after - sb_before;
let bgd_delta = (bgd0_after - bgd0_before) as u64;
assert!(sb_delta > 0, "unlink should free at least one data block");
assert_eq!(
sb_delta, bgd_delta,
"post-unlink SB ({sb_delta}) and BGD[0] ({bgd_delta}) deltas disagree"
);
fs::remove_file(path).ok();
}