#![allow(dead_code)] use anyhow::Result; use std::ffi::{OsStr, OsString}; use std::fs::OpenOptions; use std::io::{Read, Write}; use std::path::PathBuf; use thinp::file_utils; use thinp::io_engine::*; pub mod cache_xml_generator; pub mod common_args; pub mod input_arg; pub mod output_option; pub mod test_dir; pub mod thin_xml_generator; use crate::common::thin_xml_generator::{write_xml, SingleThinS}; use test_dir::TestDir; //------------------------------------------ pub mod cpp_msg { pub const FILE_NOT_FOUND: &str = "No such file or directory"; pub const MISSING_INPUT_ARG: &str = "No input file provided"; pub const MISSING_OUTPUT_ARG: &str = "No output file provided"; pub const BAD_SUPERBLOCK: &str = "bad checksum in superblock"; pub fn bad_option_hint(option: &str) -> String { format!("unrecognized option '{}'", option) } } pub mod rust_msg { pub const FILE_NOT_FOUND: &str = "Couldn't find input file"; pub const MISSING_INPUT_ARG: &str = "The following required arguments were not provided"; // TODO: be specific pub const MISSING_OUTPUT_ARG: &str = "The following required arguments were not provided"; // TODO: be specific pub const BAD_SUPERBLOCK: &str = "bad checksum in superblock"; pub fn bad_option_hint(option: &str) -> String { format!("Found argument '{}' which wasn't expected", option) } } #[cfg(not(feature = "rust_tests"))] pub use cpp_msg as msg; #[cfg(feature = "rust_tests")] pub use rust_msg as msg; //------------------------------------------ #[macro_export] macro_rules! path_to_cpp { ($name: literal) => { concat!("bin/", $name) }; } #[macro_export] macro_rules! path_to_rust { ($name: literal) => { env!(concat!("CARGO_BIN_EXE_", $name)) }; } #[cfg(not(feature = "rust_tests"))] #[macro_export] macro_rules! path_to { ($name: literal) => { path_to_cpp!($name) }; } #[cfg(feature = "rust_tests")] #[macro_export] macro_rules! path_to { ($name: literal) => { path_to_rust!($name) }; } //------------------------------------------ pub const CACHE_CHECK: &str = path_to!("cache_check"); pub const CACHE_DUMP: &str = path_to!("cache_dump"); pub const THIN_CHECK: &str = path_to!("thin_check"); pub const THIN_DELTA: &str = path_to_cpp!("thin_delta"); // TODO: rust version pub const THIN_DUMP: &str = path_to!("thin_dump"); pub const THIN_METADATA_PACK: &str = path_to_rust!("thin_metadata_pack"); // rust-only pub const THIN_METADATA_UNPACK: &str = path_to_rust!("thin_metadata_unpack"); // rust-only pub const THIN_REPAIR: &str = path_to_cpp!("thin_repair"); // TODO: rust version pub const THIN_RESTORE: &str = path_to!("thin_restore"); pub const THIN_RMAP: &str = path_to_cpp!("thin_rmap"); // TODO: rust version pub const THIN_GENERATE_METADATA: &str = path_to_cpp!("thin_generate_metadata"); // cpp-only pub const THIN_GENERATE_MAPPINGS: &str = path_to_cpp!("thin_generate_mappings"); // cpp-only pub const THIN_GENERATE_DAMAGE: &str = path_to_cpp!("thin_generate_damage"); // cpp-only //------------------------------------------ pub enum ArgType { InputArg, IoOptions, } pub trait Program<'a> { fn name() -> &'a str; fn path() -> &'a OsStr; fn usage() -> &'a str; fn arg_type() -> ArgType; // error messages fn bad_option_hint(option: &str) -> String; } pub trait InputProgram<'a>: Program<'a> { fn mk_valid_input(td: &mut TestDir) -> Result; // error messages fn missing_input_arg() -> &'a str; fn file_not_found() -> &'a str; fn corrupted_input() -> &'a str; } pub trait BinaryInputProgram<'a>: InputProgram<'a> {} pub trait OutputProgram<'a>: InputProgram<'a> { // error messages fn missing_output_arg() -> &'a str; fn file_not_found() -> &'a str; } pub trait BinaryOutputProgram<'a>: OutputProgram<'a> {} //------------------------------------------ // Returns stdout. The command must return zero. pub fn run_ok(program: S, args: I) -> Result where S: AsRef, I: IntoIterator, I::Item: Into, { let command = duct::cmd(program.as_ref(), args) .stdout_capture() .stderr_capture(); let output = command.run()?; assert!(output.status.success()); let stdout = std::str::from_utf8(&output.stdout[..]) .unwrap() .trim_end_matches(|c| c == '\n' || c == '\r') .to_string(); Ok(stdout) } // Returns the entire output. The command must return zero. pub fn run_ok_raw(program: S, args: I) -> Result where S: AsRef, I: IntoIterator, I::Item: Into, { let command = duct::cmd(program.as_ref(), args) .stdout_capture() .stderr_capture(); let output = command.run()?; assert!(output.status.success()); Ok(output) } // Returns stderr, a non zero status must be returned pub fn run_fail(program: S, args: I) -> Result where S: AsRef, I: IntoIterator, I::Item: Into, { let command = duct::cmd(program.as_ref(), args) .stdout_capture() .stderr_capture(); let output = command.unchecked().run()?; assert!(!output.status.success()); let stderr = std::str::from_utf8(&output.stderr[..]).unwrap().to_string(); Ok(stderr) } // Returns the entire output, a non zero status must be returned pub fn run_fail_raw(program: S, args: I) -> Result where S: AsRef, I: IntoIterator, I::Item: Into, { let command = duct::cmd(program.as_ref(), args) .stdout_capture() .stderr_capture(); let output = command.unchecked().run()?; assert!(!output.status.success()); Ok(output) } //------------------------------------------ pub fn mk_valid_xml(td: &mut TestDir) -> Result { let xml = td.mk_path("meta.xml"); let mut gen = SingleThinS::new(0, 1024, 2048, 2048); write_xml(&xml, &mut gen)?; Ok(xml) } pub fn mk_valid_md(td: &mut TestDir) -> Result { let xml = td.mk_path("meta.xml"); let md = td.mk_path("meta.bin"); let mut gen = SingleThinS::new(0, 1024, 20480, 20480); write_xml(&xml, &mut gen)?; let _file = file_utils::create_sized_file(&md, 4096 * 4096); let args = ["-i", xml.to_str().unwrap(), "-o", md.to_str().unwrap()]; run_ok(THIN_RESTORE, &args)?; Ok(md) } pub fn mk_zeroed_md(td: &mut TestDir) -> Result { let md = td.mk_path("meta.bin"); eprintln!("path = {:?}", md); let _file = file_utils::create_sized_file(&md, 1024 * 1024 * 16); Ok(md) } pub fn superblock_all_zeroes(path: &PathBuf) -> Result { let mut input = OpenOptions::new().read(true).write(false).open(path)?; let mut buf = vec![0; 4096]; input.read_exact(&mut buf[0..])?; for b in buf { if b != 0 { return Ok(false); } } Ok(true) } pub fn damage_superblock(path: &PathBuf) -> Result<()> { let mut output = OpenOptions::new().read(false).write(true).open(path)?; let buf = [0u8; 512]; output.write_all(&buf)?; Ok(()) } //----------------------------------------------- // FIXME: replace mk_valid_md with this? pub fn prep_metadata(td: &mut TestDir) -> Result { let md = mk_zeroed_md(td)?; let args = [ "-o", md.to_str().unwrap(), "--format", "--nr-data-blocks", "102400", ]; run_ok(THIN_GENERATE_METADATA, &args)?; // Create a 2GB device let args = ["-o", md.to_str().unwrap(), "--create-thin", "1"]; run_ok(THIN_GENERATE_METADATA, &args)?; let args = [ "-o", md.to_str().unwrap(), "--dev-id", "1", "--size", "2097152", "--rw=randwrite", "--seq-nr=16", ]; run_ok(THIN_GENERATE_MAPPINGS, &args)?; // Take a few snapshots. let mut snap_id = 2; for _i in 0..10 { // take a snapshot let args = [ "-o", md.to_str().unwrap(), "--create-snap", &snap_id.to_string(), "--origin", "1", ]; run_ok(THIN_GENERATE_METADATA, &args)?; // partially overwrite the origin (64MB) let args = [ "-o", md.to_str().unwrap(), "--dev-id", "1", "--size", "2097152", "--io-size", "131072", "--rw=randwrite", "--seq-nr=16", ]; run_ok(THIN_GENERATE_MAPPINGS, &args)?; snap_id += 1; } Ok(md) } pub fn set_needs_check(md: &PathBuf) -> Result<()> { let args = ["-o", md.to_str().unwrap(), "--set-needs-check"]; run_ok(THIN_GENERATE_METADATA, &args)?; Ok(()) } pub fn generate_metadata_leaks( md: &PathBuf, nr_blocks: u64, expected: u32, actual: u32, ) -> Result<()> { let args = [ "-o", md.to_str().unwrap(), "--create-metadata-leaks", "--nr-blocks", &nr_blocks.to_string(), "--expected", &expected.to_string(), "--actual", &actual.to_string(), ]; run_ok(THIN_GENERATE_DAMAGE, &args)?; Ok(()) } pub fn get_needs_check(md: &PathBuf) -> Result { use thinp::thin::superblock::*; let engine = SyncIoEngine::new(&md, 1, false)?; let sb = read_superblock(&engine, SUPERBLOCK_LOCATION)?; Ok(sb.flags.needs_check) } pub fn md5(md: &PathBuf) -> Result { let output = duct::cmd!("md5sum", "-b", &md).stdout_capture().run()?; let csum = std::str::from_utf8(&output.stdout[0..])?.to_string(); let csum = csum.split_ascii_whitespace().next().unwrap().to_string(); Ok(csum) } // This checksums the file before and after the thunk is run to // ensure it is unchanged. pub fn ensure_untouched(p: &PathBuf, thunk: F) -> Result<()> where F: Fn() -> Result<()>, { let csum = md5(p)?; thunk()?; assert_eq!(csum, md5(p)?); Ok(()) } pub fn ensure_superblock_zeroed(p: &PathBuf, thunk: F) -> Result<()> where F: Fn() -> Result<()>, { thunk()?; assert!(superblock_all_zeroes(p)?); Ok(()) } //------------------------------------------