use std::path::{Path, PathBuf};
use std::sync::Arc;
use fs_btrfs::fs::Filesystem;
use fs_btrfs::super_write::Commit;
use fs_btrfs_test_support::{
assert_btrfs_check_clean, fixture, fixtures_matching, guest_kernel_probe,
guest_kernel_write_ok, spans_several_devices, temp_path,
};
use fs_core::{BlockDevice, FileDevice};
const PAYLOAD: &str = "kernel round-trip";
fn single_device_fixtures() -> Vec<PathBuf> {
let all = fixtures_matching("btrfs-");
let (single, pooled): (Vec<PathBuf>, Vec<PathBuf>) = all
.into_iter()
.partition(|image| !spans_several_devices(image));
for image in &pooled {
println!(
"[kernel vm] set aside {}: it is one device of a multi-device filesystem",
name(image)
);
}
assert!(
!single.is_empty(),
"every fixture spans several devices, so this would mount nothing"
);
single
}
fn name(path: &Path) -> String {
path.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned()
}
#[test]
fn the_kernel_mounts_every_fixture_and_reads_back_what_it_wrote() {
let mut mounted = 0;
let mut refused = Vec::new();
let mut noisy = Vec::new();
for image in single_device_fixtures() {
let probe = guest_kernel_probe(&image.to_string_lossy(), PAYLOAD);
if probe.readback != PAYLOAD {
refused.push(format!(
"{}: read back {:?}, not {PAYLOAD:?}",
name(&image),
probe.readback
));
}
if !probe.complaints.is_empty() {
noisy.push(format!(
"{}:\n {}",
name(&image),
probe.complaints.join("\n ")
));
}
mounted += 1;
}
assert!(
refused.is_empty(),
"the in-kernel btrfs driver did not round-trip these images:\n {}",
refused.join("\n ")
);
assert!(
noisy.is_empty(),
"the kernel mounted these images while complaining about them, which is not a \
pass — a mount Linux tolerates is not the same as an image Linux agrees with:\n {}",
noisy.join("\n ")
);
assert!(mounted > 0, "no image was mounted, so this checked nothing");
println!("[kernel vm] {mounted} fixtures mounted, written and read back cleanly");
}
#[test]
fn a_transaction_this_driver_wrote_is_accepted_by_the_checker_and_the_kernel() {
let mut done = 0;
for (label, suffix) in [("default geometry", ""), ("sha256+dup", "-sha256-dup")] {
let source = fixture(&format!("btrfs-cow-before{suffix}.img"));
let written = PathBuf::from(temp_path!("written{}.img", suffix));
std::fs::copy(&source, &written).expect("copying the fixture to write on");
write_one_transaction(&written, label);
assert_btrfs_check_clean(&written, &format!("{label}: after our transaction"));
let out = guest_kernel_write_ok(
&written.to_string_lossy(),
label,
"printf 'hello from the kernel\\n' > \"$MNT/newfile\"\n\
sync\n\
cat \"$MNT/newfile\"",
);
assert_eq!(
out.trim(),
"hello from the kernel",
"[{label}] the kernel did not read back the file it had just written"
);
assert_btrfs_check_clean(&written, &format!("{label}: after the kernel used it"));
let _ = std::fs::remove_file(&written);
done += 1;
}
assert_eq!(done, 2, "both geometries must be written and judged");
}
fn write_one_transaction(image: &Path, label: &str) {
let dev = Arc::new(FileDevice::open_rw(image).expect("opening the copy read-write"));
let fs = Filesystem::mount_rw(dev.clone() as Arc<dyn BlockDevice>).expect("mounting");
let generation = fs.superblock().generation + 1;
let root = fs.superblock().root;
let plan = fs
.plan_transaction_closed(&[root], 8)
.unwrap_or_else(|error| panic!("[{label}] planning a closed transaction: {error}"));
assert!(
!plan.rewrites.is_empty(),
"[{label}] the plan moved nothing, so the commit below would assert nothing"
);
let blocks = fs
.render_plan(&plan, generation)
.unwrap_or_else(|error| panic!("[{label}] rendering the plan: {error}"));
let new_root = fs
.planned_root(&plan)
.expect("the plan moves the root tree");
fs.commit(
&blocks,
&Commit {
generation,
root: new_root,
root_level: None,
bytes_used: None,
chunk_root: None,
chunk_root_generation: None,
invalidate_free_space_tree: true,
},
)
.unwrap_or_else(|error| panic!("[{label}] committing: {error}"));
println!(
"[kernel vm] {label}: committed generation {generation}, root tree at {new_root}, \
{} blocks rewritten",
plan.rewrites.len()
);
}