#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::OnceLock;
const BIN: Option<&str> = option_env!("CARGO_BIN_EXE_rust-fs-ext4");
const NO_BIN: &str = "the rust-fs-ext4 binary is built only with `--features cli`. Run the \
tests through scripts/test.sh, which passes it, or add `--features cli` to cargo test.";
pub fn bin() -> &'static str {
BIN.expect(NO_BIN)
}
pub fn entry() -> Command {
Command::new(BIN.expect(NO_BIN))
}
pub fn tool(name: &str) -> Command {
use std::os::unix::process::CommandExt;
let mut cmd = Command::new(BIN.expect(NO_BIN));
cmd.arg0(name);
cmd
}
pub fn names_dir() -> &'static Path {
static DIR: OnceLock<PathBuf> = OnceLock::new();
DIR.get_or_init(|| {
let dir = PathBuf::from(fs_ext4_test_support::temp_path!(
"cli-names-{}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create the names directory");
let mut names = dotted_names();
names.push("rust-fs-ext4".to_string());
for name in names {
std::os::unix::fs::symlink(bin(), dir.join(&name))
.unwrap_or_else(|e| panic!("link {name}: {e}"));
}
dir
})
}
pub fn dotted_names() -> Vec<String> {
let out = entry()
.args(["generate", "names"])
.output()
.expect("run rust-fs-ext4 generate names");
assert!(out.status.success(), "generate names failed: {out:?}");
String::from_utf8(out.stdout)
.expect("names are UTF-8")
.lines()
.map(str::to_string)
.collect()
}
pub fn image_path(tag: &str) -> String {
static N: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
let n = N.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let path = fs_ext4_test_support::temp_path!("cli-{}-{n}-{tag}.img", std::process::id());
let _ = std::fs::remove_file(&path);
path
}
pub fn sized_image(tag: &str, size: u64) -> String {
let path = image_path(tag);
std::fs::File::create(&path)
.and_then(|f| f.set_len(size))
.unwrap_or_else(|e| panic!("size {path}: {e}"));
path
}
pub fn stdout(out: &Output) -> String {
String::from_utf8_lossy(&out.stdout).into_owned()
}
pub fn stderr(out: &Output) -> String {
String::from_utf8_lossy(&out.stderr).into_owned()
}
#[track_caller]
pub fn ok(cmd: &mut Command) -> Output {
let out = cmd.output().expect("spawn");
assert!(
out.status.success(),
"{cmd:?} failed ({:?})\nstdout:\n{}\nstderr:\n{}",
out.status.code(),
stdout(&out),
stderr(&out)
);
out
}
#[track_caller]
pub fn json_field(json: &str, key: &str) -> String {
let needle = format!("\"{key}\": ");
let start = json
.find(&needle)
.unwrap_or_else(|| panic!("no {key:?} in:\n{json}"))
+ needle.len();
let rest = &json[start..];
if let Some(stripped) = rest.strip_prefix('"') {
let end = stripped.find('"').expect("closing quote");
stripped[..end].to_string()
} else {
rest.split([',', '\n', '}'])
.next()
.unwrap()
.trim()
.to_string()
}
}
pub fn corrupt_group_free_blocks(image: &str, extra: u16) {
use fs_ext4::block_io::{BlockDevice, FileDevice};
let fs = fs_ext4::Filesystem::mount(std::sync::Arc::new(
FileDevice::open(image).expect("open image"),
))
.expect("mount image");
let block = u64::from(fs.sb.block_size());
let desc_size = usize::from(fs.sb.desc_size.max(32));
let at = (u64::from(fs.sb.first_data_block) + 1) * block;
let dev = FileDevice::open_rw(image).expect("open image rw");
let mut desc = vec![0u8; desc_size];
dev.read_at(at, &mut desc).expect("read descriptor 0");
let free = u16::from_le_bytes([desc[0x0C], desc[0x0D]]);
desc[0x0C..0x0E].copy_from_slice(&free.wrapping_add(extra).to_le_bytes());
if let Some(csum) = fs_ext4::checksum::group_desc_csum(&fs.sb, &fs.csum, 0, &desc) {
desc[0x1E..0x20].copy_from_slice(&csum.to_le_bytes());
}
dev.write_at(at, &desc).expect("write descriptor 0");
dev.flush().expect("flush");
}
pub fn destroy_root_extent_header(image: &str) {
use fs_ext4::block_io::{BlockDevice, FileDevice};
let fs = fs_ext4::Filesystem::mount(std::sync::Arc::new(
FileDevice::open(image).expect("open image"),
))
.expect("mount image");
let (block, offset) =
fs_ext4::bgd::locate_inode(&fs.sb, &fs.groups, 2).expect("locate the root inode");
let at = block * u64::from(fs.sb.block_size()) + u64::from(offset);
let dev = FileDevice::open_rw(image).expect("open image rw");
let mut raw = vec![0u8; usize::from(fs.sb.inode_size)];
dev.read_at(at, &mut raw).expect("read the root inode");
raw[0x28..0x28 + 12].fill(0);
let generation = u32::from_le_bytes(raw[0x64..0x68].try_into().unwrap());
if let Some((lo, hi)) = fs.csum.compute_inode_checksum(2, generation, &raw) {
raw[0x7C..0x7E].copy_from_slice(&lo.to_le_bytes());
let extra_isize = u16::from_le_bytes([raw[0x80], raw[0x81]]);
if raw.len() > 0x84 && extra_isize >= 4 {
raw[0x82..0x84].copy_from_slice(&hi.to_le_bytes());
}
}
dev.write_at(at, &raw).expect("write the root inode");
dev.flush().expect("flush");
}
pub fn pattern(len: usize, seed: u32) -> Vec<u8> {
let mut x = seed.wrapping_mul(2_654_435_761).wrapping_add(1);
(0..len)
.map(|_| {
x = x.wrapping_mul(1_103_515_245).wrapping_add(12_345);
(x >> 16) as u8
})
.collect()
}
pub fn fs_write(image: &str, path: &str, bytes: &[u8]) -> Output {
use std::io::Write;
let mut child = tool("fs.ext4")
.args([image, "write", path])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("spawn fs.ext4 write");
child
.stdin
.take()
.expect("stdin")
.write_all(bytes)
.expect("feed stdin");
child.wait_with_output().expect("wait for fs.ext4 write")
}
pub fn write_cases() -> Vec<(&'static str, Vec<u8>)> {
vec![
("/empty", Vec::new()),
("/one", pattern(1, 1)),
("/f4095", pattern(4095, 2)),
("/f4096", pattern(4096, 3)),
("/f4097", pattern(4097, 4)),
("/big", pattern(1 << 20, 5)),
("/d/e/deep", pattern(5000, 6)),
("/replaced", pattern(100, 8)),
]
}
pub fn written_image(tag: &str) -> String {
let img = image_path(tag);
ok(tool("mkfs.ext4").args(["-q", "--text", "--size", "64M", &img]));
ok(tool("fs.ext4").args([&img, "mkdir", "/d"]));
ok(tool("fs.ext4").args([&img, "mkdir", "/d/e"]));
let first = fs_write(&img, "/replaced", &pattern(9000, 7));
assert!(first.status.success(), "{}", stderr(&first));
for (path, bytes) in write_cases() {
let out = fs_write(&img, path, &bytes);
assert!(out.status.success(), "write {path}: {}", stderr(&out));
}
img
}