From 024554c987ba11826448e50808ccfb85ce22e194 Mon Sep 17 00:00:00 2001 From: Joe Thornber Date: Mon, 11 Oct 2021 12:07:26 +0100 Subject: [PATCH] Merge rust tools into a single pdata_tools exe --- Cargo.lock | 8 +- Cargo.toml | 2 +- src/bin/pdata_tools.rs | 69 ++++++ src/cache/check.rs | 10 +- src/{bin => commands}/cache_check.rs | 37 ++- src/{bin => commands}/cache_dump.rs | 22 +- src/{bin => commands}/cache_repair.rs | 33 ++- src/{bin => commands}/cache_restore.rs | 37 +-- src/commands/mod.rs | 12 + src/{bin => commands}/thin_check.rs | 34 +-- src/{bin => commands}/thin_dump.rs | 22 +- src/{bin => commands}/thin_metadata_pack.rs | 21 +- src/{bin => commands}/thin_metadata_unpack.rs | 15 +- src/{bin => commands}/thin_repair.rs | 40 ++-- src/{bin => commands}/thin_restore.rs | 37 +-- src/{bin => commands}/thin_shrink.rs | 18 +- src/commands/utils.rs | 91 ++++++++ src/file_utils.rs | 61 ++--- src/lib.rs | 1 + src/report.rs | 17 ++ src/thin/check.rs | 4 +- tests/cache_check.rs | 56 +++-- tests/cache_dump.rs | 36 ++- tests/cache_repair.rs | 29 ++- tests/cache_restore.rs | 52 +++-- tests/common/cache.rs | 2 +- tests/common/common_args.rs | 10 +- tests/common/input_arg.rs | 21 +- tests/common/output_option.rs | 12 +- tests/common/process.rs | 46 +--- tests/common/program.rs | 6 +- tests/common/target.rs | 211 ++++++++++++------ tests/common/thin.rs | 16 +- tests/thin_check.rs | 160 +++++++------ tests/thin_delta.rs | 16 +- tests/thin_dump.rs | 37 +-- tests/thin_metadata_pack.rs | 18 +- tests/thin_metadata_unpack.rs | 27 +-- tests/thin_repair.rs | 24 +- tests/thin_restore.rs | 14 +- tests/thin_rmap.rs | 23 +- 41 files changed, 840 insertions(+), 567 deletions(-) create mode 100644 src/bin/pdata_tools.rs rename src/{bin => commands}/cache_check.rs (75%) rename src/{bin => commands}/cache_dump.rs (76%) rename src/{bin => commands}/cache_repair.rs (72%) rename src/{bin => commands}/cache_restore.rs (64%) create mode 100644 src/commands/mod.rs rename src/{bin => commands}/thin_check.rs (84%) rename src/{bin => commands}/thin_dump.rs (90%) rename src/{bin => commands}/thin_metadata_pack.rs (67%) rename src/{bin => commands}/thin_metadata_unpack.rs (74%) rename src/{bin => commands}/thin_repair.rs (75%) rename src/{bin => commands}/thin_restore.rs (64%) rename src/{bin => commands}/thin_shrink.rs (85%) create mode 100644 src/commands/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 40de1e4..75f2464 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.97" +version = "0.2.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" [[package]] name = "log" @@ -421,9 +421,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.21.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" +checksum = "e7555d6c7164cc913be1ce7f95cbecdabda61eb2ccd89008524af306fb7f5031" dependencies = [ "bitflags", "cc", diff --git a/Cargo.toml b/Cargo.toml index 8830478..def9356 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ flate2 = "1.0" io-uring = "0.4" indicatif = "0.16" libc = "0.2" -nix = "0.21" +nix = "0.22" nom = "6.2" num_cpus = "1.13" num-derive = "0.3" diff --git a/src/bin/pdata_tools.rs b/src/bin/pdata_tools.rs new file mode 100644 index 0000000..0ba337c --- /dev/null +++ b/src/bin/pdata_tools.rs @@ -0,0 +1,69 @@ +use anyhow::{anyhow, ensure, Result}; +use std::ffi::OsString; +use std::path::Path; +use std::process::exit; +use thinp::commands::*; + +fn name_eq(name: &Path, cmd: &str) -> bool { + name == Path::new(cmd) +} + +fn main_() -> Result<()> { + let mut args = std::env::args_os(); + ensure!(args.len() > 0); + + let mut os_name = args.next().unwrap(); + let mut name = Path::new(&os_name); + name = Path::new(name.file_name().unwrap()); + + if name == Path::new("pdata_tools") { + os_name = args.next().unwrap(); + name = Path::new(&os_name); + } + + let mut new_args = vec![OsString::from(&name)]; + for a in args.into_iter() { + new_args.push(OsString::from(a)); + } + + if name_eq(name, "cache_check") { + cache_check::run(&new_args); + } else if name_eq(name, "cache_dump") { + cache_dump::run(&new_args); + } else if name_eq(name, "cache_repair") { + cache_repair::run(&new_args); + } else if name_eq(name, "cache_restore") { + cache_restore::run(&new_args); + } else if name_eq(name, "thin_check") { + thin_check::run(&new_args); + } else if name_eq(name, "thin_dump") { + thin_dump::run(&new_args); + } else if name_eq(name, "thin_metadata_pack") { + thin_metadata_pack::run(&new_args); + } else if name_eq(name, "thin_metadata_unpack") { + thin_metadata_unpack::run(&new_args); + } else if name_eq(name, "thin_repair") { + thin_repair::run(&new_args); + } else if name_eq(name, "thin_restore") { + thin_restore::run(&new_args); + } else if name_eq(name, "thin_shrink") { + thin_shrink::run(&new_args); + } else { + return Err(anyhow!("unrecognised command")); + } + + Ok(()) +} + +fn main() { + let code = match main_() { + Ok(()) => 0, + Err(_) => { + // We don't print out the error since -q may be set + // eprintln!("{}", e); + 1 + } + }; + + exit(code) +} diff --git a/src/cache/check.rs b/src/cache/check.rs index 82e37c1..17632cc 100644 --- a/src/cache/check.rs +++ b/src/cache/check.rs @@ -6,6 +6,7 @@ use std::sync::{Arc, Mutex}; use crate::cache::hint::*; use crate::cache::mapping::*; use crate::cache::superblock::*; +use crate::commands::utils::*; use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine}; use crate::pdata::array::{self, ArrayBlock, ArrayError}; use crate::pdata::array_walker::*; @@ -267,7 +268,14 @@ pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> { let metadata_sm = core_sm(engine.get_nr_blocks(), u8::MAX as u32); inc_superblock(&metadata_sm)?; - let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?; + let sb = match read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION) { + Ok(sb) => sb, + Err(e) => { + check_not_xml(&opts.dev, &opts.report); + return Err(e); + } + }; + check_superblock(&sb)?; if opts.sb_only { diff --git a/src/bin/cache_check.rs b/src/commands/cache_check.rs similarity index 75% rename from src/bin/cache_check.rs rename to src/commands/cache_check.rs index d37c07a..9c74a34 100644 --- a/src/bin/cache_check.rs +++ b/src/commands/cache_check.rs @@ -1,5 +1,4 @@ extern crate clap; -extern crate thinp; use atty::Stream; use clap::{App, Arg}; @@ -7,15 +6,15 @@ use std::path::Path; use std::process; use std::sync::Arc; -use thinp::cache::check::{check, CacheCheckOptions}; -use thinp::file_utils; -use thinp::report::*; +use crate::cache::check::{check, CacheCheckOptions}; +use crate::report::*; +use crate::commands::utils::*; //------------------------------------------ -fn main() { +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("cache_check") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) // flags .arg( Arg::with_name("ASYNC_IO") @@ -44,11 +43,6 @@ fn main() { .help("Only check the superblock.") .long("super-block-only"), ) - .arg( - Arg::with_name("SKIP_MAPPINGS") - .help("Don't check the mapping array") - .long("skip-mappings"), - ) .arg( Arg::with_name("SKIP_HINTS") .help("Don't check the hint array") @@ -67,22 +61,19 @@ fn main() { .index(1), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); let input_file = Path::new(matches.value_of("INPUT").unwrap()); - if let Err(e) = file_utils::is_file_or_blk(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - process::exit(1); - } - - let report; - if matches.is_present("QUIET") { - report = std::sync::Arc::new(mk_quiet_report()); + let report = if matches.is_present("QUIET") { + std::sync::Arc::new(mk_quiet_report()) } else if atty::is(Stream::Stdout) { - report = std::sync::Arc::new(mk_progress_bar_report()); + std::sync::Arc::new(mk_progress_bar_report()) } else { - report = Arc::new(mk_simple_report()); - } + Arc::new(mk_simple_report()) + }; + + check_input_file(input_file, &report); + check_file_not_tiny(input_file, &report); let opts = CacheCheckOptions { dev: &input_file, diff --git a/src/bin/cache_dump.rs b/src/commands/cache_dump.rs similarity index 76% rename from src/bin/cache_dump.rs rename to src/commands/cache_dump.rs index b6875c9..ebf4156 100644 --- a/src/bin/cache_dump.rs +++ b/src/commands/cache_dump.rs @@ -1,17 +1,17 @@ extern crate clap; -extern crate thinp; use clap::{App, Arg}; use std::path::Path; use std::process; -use thinp::cache::dump::{dump, CacheDumpOptions}; -use thinp::file_utils; + +use crate::cache::dump::{dump, CacheDumpOptions}; +use crate::commands::utils::*; //------------------------------------------ -fn main() { +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("cache_dump") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Dump the cache metadata to stdout in XML format") // flags .arg( @@ -42,7 +42,7 @@ fn main() { .index(1), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); let input_file = Path::new(matches.value_of("INPUT").unwrap()); let output_file = if matches.is_present("OUTPUT") { Some(Path::new(matches.value_of("OUTPUT").unwrap())) @@ -50,10 +50,12 @@ fn main() { None }; - if let Err(e) = file_utils::is_file_or_blk(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - process::exit(1); - } + // Create a temporary report just in case these checks + // need to report anything. + let report = std::sync::Arc::new(crate::report::mk_simple_report()); + check_input_file(input_file, &report); + check_file_not_tiny(input_file, &report); + drop(report); let opts = CacheDumpOptions { input: input_file, diff --git a/src/bin/cache_repair.rs b/src/commands/cache_repair.rs similarity index 72% rename from src/bin/cache_repair.rs rename to src/commands/cache_repair.rs index 646516b..fd569a8 100644 --- a/src/bin/cache_repair.rs +++ b/src/commands/cache_repair.rs @@ -1,18 +1,18 @@ extern crate clap; -extern crate thinp; use atty::Stream; use clap::{App, Arg}; use std::path::Path; use std::process; use std::sync::Arc; -use thinp::cache::repair::{repair, CacheRepairOptions}; -use thinp::file_utils; -use thinp::report::*; -fn main() { +use crate::cache::repair::{repair, CacheRepairOptions}; +use crate::commands::utils::*; +use crate::report::*; + +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("cache_repair") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Repair binary cache metadata, and write it to a different device or file") // flags .arg( @@ -45,24 +45,19 @@ fn main() { .required(true), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); let input_file = Path::new(matches.value_of("INPUT").unwrap()); let output_file = Path::new(matches.value_of("OUTPUT").unwrap()); - if let Err(e) = file_utils::is_file_or_blk(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - process::exit(1); - } - - let report; - - if matches.is_present("QUIET") { - report = std::sync::Arc::new(mk_quiet_report()); + let report = if matches.is_present("QUIET") { + std::sync::Arc::new(mk_quiet_report()) } else if atty::is(Stream::Stdout) { - report = std::sync::Arc::new(mk_progress_bar_report()); + std::sync::Arc::new(mk_progress_bar_report()) } else { - report = Arc::new(mk_simple_report()); - } + Arc::new(mk_simple_report()) + }; + + check_input_file(input_file, &report); let opts = CacheRepairOptions { input: &input_file, diff --git a/src/bin/cache_restore.rs b/src/commands/cache_restore.rs similarity index 64% rename from src/bin/cache_restore.rs rename to src/commands/cache_restore.rs index 66b8359..f40c86c 100644 --- a/src/bin/cache_restore.rs +++ b/src/commands/cache_restore.rs @@ -1,18 +1,15 @@ extern crate clap; -extern crate thinp; -use atty::Stream; use clap::{App, Arg}; use std::path::Path; use std::process; -use std::sync::Arc; -use thinp::cache::restore::{restore, CacheRestoreOptions}; -use thinp::file_utils; -use thinp::report::*; -fn main() { +use crate::cache::restore::{restore, CacheRestoreOptions}; +use crate::commands::utils::*; + +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("cache_restore") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Convert XML format metadata to binary.") // flags .arg( @@ -45,29 +42,13 @@ fn main() { .required(true), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); let input_file = Path::new(matches.value_of("INPUT").unwrap()); let output_file = Path::new(matches.value_of("OUTPUT").unwrap()); - if let Err(e) = file_utils::is_file(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - process::exit(1); - } - - if let Err(e) = file_utils::check_output_file_requirements(output_file) { - eprintln!("{}", e); - process::exit(1); - } - - let report; - - if matches.is_present("QUIET") { - report = std::sync::Arc::new(mk_quiet_report()); - } else if atty::is(Stream::Stdout) { - report = std::sync::Arc::new(mk_progress_bar_report()); - } else { - report = Arc::new(mk_simple_report()); - } + let report = mk_report(matches.is_present("QUIET")); + check_input_file(input_file, &report); + check_output_file(output_file, &report); let opts = CacheRestoreOptions { input: &input_file, diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..ffc41bb --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,12 @@ +pub mod cache_check; +pub mod cache_dump; +pub mod cache_repair; +pub mod cache_restore; +pub mod thin_check; +pub mod thin_dump; +pub mod thin_metadata_pack; +pub mod thin_metadata_unpack; +pub mod thin_repair; +pub mod thin_restore; +pub mod thin_shrink; +pub mod utils; diff --git a/src/bin/thin_check.rs b/src/commands/thin_check.rs similarity index 84% rename from src/bin/thin_check.rs rename to src/commands/thin_check.rs index d04cc62..01135d7 100644 --- a/src/bin/thin_check.rs +++ b/src/commands/thin_check.rs @@ -1,19 +1,17 @@ extern crate clap; -extern crate thinp; -use atty::Stream; use clap::{App, Arg}; use std::path::Path; use std::process; use std::sync::Arc; -use thinp::file_utils; -use thinp::io_engine::*; -use thinp::report::*; -use thinp::thin::check::{check, ThinCheckOptions, MAX_CONCURRENT_IO}; -fn main() { +use crate::io_engine::*; +use crate::thin::check::{check, ThinCheckOptions, MAX_CONCURRENT_IO}; +use crate::commands::utils::*; + +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("thin_check") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Validates thin provisioning metadata on a device or file.") // flags .arg( @@ -90,23 +88,13 @@ fn main() { .index(1), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args.into_iter()); let input_file = Path::new(matches.value_of("INPUT").unwrap()); - if let Err(e) = file_utils::is_file_or_blk(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - process::exit(1); - } - - let report; - - if matches.is_present("QUIET") { - report = std::sync::Arc::new(mk_quiet_report()); - } else if atty::is(Stream::Stdout) { - report = std::sync::Arc::new(mk_progress_bar_report()); - } else { - report = Arc::new(mk_simple_report()); - } + let report = mk_report(matches.is_present("QUIET")); + check_input_file(input_file, &report); + check_file_not_tiny(input_file, &report); + check_not_xml(input_file, &report); let engine: Arc; let writable = matches.is_present("AUTO_REPAIR") || matches.is_present("CLEAR_NEEDS_CHECK"); diff --git a/src/bin/thin_dump.rs b/src/commands/thin_dump.rs similarity index 90% rename from src/bin/thin_dump.rs rename to src/commands/thin_dump.rs index 0a6a8b8..ec8541d 100644 --- a/src/bin/thin_dump.rs +++ b/src/commands/thin_dump.rs @@ -1,19 +1,19 @@ extern crate clap; -extern crate thinp; use atty::Stream; use clap::{App, Arg}; use std::path::Path; use std::process; use std::sync::Arc; -use thinp::file_utils; -use thinp::report::*; -use thinp::thin::dump::{dump, ThinDumpOptions}; -use thinp::thin::metadata_repair::SuperblockOverrides; -fn main() { +use crate::commands::utils::*; +use crate::report::*; +use crate::thin::dump::{dump, ThinDumpOptions}; +use crate::thin::metadata_repair::SuperblockOverrides; + +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("thin_dump") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Dump thin-provisioning metadata to stdout in XML format") // flags .arg( @@ -80,7 +80,7 @@ fn main() { .index(1), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); let input_file = Path::new(matches.value_of("INPUT").unwrap()); let output_file = if matches.is_present("OUTPUT") { Some(Path::new(matches.value_of("OUTPUT").unwrap())) @@ -88,10 +88,8 @@ fn main() { None }; - if let Err(e) = file_utils::is_file_or_blk(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - process::exit(1); - } + let report = std::sync::Arc::new(mk_simple_report()); + check_input_file(input_file, &report); let transaction_id = matches.value_of("TRANSACTION_ID").map(|s| { s.parse::().unwrap_or_else(|_| { diff --git a/src/bin/thin_metadata_pack.rs b/src/commands/thin_metadata_pack.rs similarity index 67% rename from src/bin/thin_metadata_pack.rs rename to src/commands/thin_metadata_pack.rs index bc72af0..8b4cfa4 100644 --- a/src/bin/thin_metadata_pack.rs +++ b/src/commands/thin_metadata_pack.rs @@ -1,14 +1,15 @@ extern crate clap; -extern crate thinp; use clap::{App, Arg}; use std::path::Path; use std::process::exit; -use thinp::file_utils; -fn main() { +use crate::commands::utils::*; +use crate::report::*; + +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("thin_metadata_pack") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Produces a compressed file of thin metadata. Only packs metadata blocks that are actually used.") .arg(Arg::with_name("INPUT") .help("Specify thinp metadata binary device/file") @@ -23,17 +24,15 @@ fn main() { .value_name("FILE") .takes_value(true)); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); let input_file = Path::new(matches.value_of("INPUT").unwrap()); let output_file = Path::new(matches.value_of("OUTPUT").unwrap()); - if let Err(e) = file_utils::is_file_or_blk(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - exit(1); - } + let report = std::sync::Arc::new(mk_simple_report()); + check_input_file(input_file, &report); - if let Err(reason) = thinp::pack::toplevel::pack(&input_file, &output_file) { - println!("Application error: {}\n", reason); + if let Err(reason) = crate::pack::toplevel::pack(&input_file, &output_file) { + report.fatal(&format!("Application error: {}\n", reason)); exit(1); } } diff --git a/src/bin/thin_metadata_unpack.rs b/src/commands/thin_metadata_unpack.rs similarity index 74% rename from src/bin/thin_metadata_unpack.rs rename to src/commands/thin_metadata_unpack.rs index b2463df..6a2cf8d 100644 --- a/src/bin/thin_metadata_unpack.rs +++ b/src/commands/thin_metadata_unpack.rs @@ -1,16 +1,15 @@ extern crate clap; -extern crate thinp; use clap::{App, Arg}; use std::path::Path; use std::process; -use thinp::file_utils; +use crate::file_utils; use std::process::exit; -fn main() { +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("thin_metadata_unpack") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Unpack a compressed file of thin metadata.") .arg( Arg::with_name("INPUT") @@ -29,16 +28,16 @@ fn main() { .takes_value(true), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); let input_file = Path::new(matches.value_of("INPUT").unwrap()); let output_file = Path::new(matches.value_of("OUTPUT").unwrap()); - if let Err(e) = file_utils::is_file(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); + if !file_utils::is_file(input_file) { + eprintln!("Invalid input file '{}'.", input_file.display()); exit(1); } - if let Err(reason) = thinp::pack::toplevel::unpack(&input_file, &output_file) { + if let Err(reason) = crate::pack::toplevel::unpack(&input_file, &output_file) { eprintln!("Application error: {}", reason); process::exit(1); } diff --git a/src/bin/thin_repair.rs b/src/commands/thin_repair.rs similarity index 75% rename from src/bin/thin_repair.rs rename to src/commands/thin_repair.rs index d2de7a3..39ce159 100644 --- a/src/bin/thin_repair.rs +++ b/src/commands/thin_repair.rs @@ -1,19 +1,16 @@ extern crate clap; -extern crate thinp; -use atty::Stream; use clap::{App, Arg}; use std::path::Path; use std::process; -use std::sync::Arc; -use thinp::file_utils; -use thinp::report::*; -use thinp::thin::metadata_repair::SuperblockOverrides; -use thinp::thin::repair::{repair, ThinRepairOptions}; -fn main() { +use crate::commands::utils::*; +use crate::thin::metadata_repair::SuperblockOverrides; +use crate::thin::repair::{repair, ThinRepairOptions}; + +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("thin_repair") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Repair thin-provisioning metadata, and write it to different device or file") // flags .arg( @@ -64,46 +61,35 @@ fn main() { .value_name("NUM"), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); let input_file = Path::new(matches.value_of("INPUT").unwrap()); let output_file = Path::new(matches.value_of("OUTPUT").unwrap()); - if let Err(e) = file_utils::is_file_or_blk(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - process::exit(1); - } + let report = mk_report(matches.is_present("QUIET")); + check_input_file(input_file, &report); + check_output_file(output_file, &report); let transaction_id = matches.value_of("TRANSACTION_ID").map(|s| { s.parse::().unwrap_or_else(|_| { - eprintln!("Couldn't parse transaction_id"); + report.fatal("Couldn't parse transaction_id"); process::exit(1); }) }); let data_block_size = matches.value_of("DATA_BLOCK_SIZE").map(|s| { s.parse::().unwrap_or_else(|_| { - eprintln!("Couldn't parse data_block_size"); + report.fatal("Couldn't parse data_block_size"); process::exit(1); }) }); let nr_data_blocks = matches.value_of("NR_DATA_BLOCKS").map(|s| { s.parse::().unwrap_or_else(|_| { - eprintln!("Couldn't parse nr_data_blocks"); + report.fatal("Couldn't parse nr_data_blocks"); process::exit(1); }) }); - let report; - - if matches.is_present("QUIET") { - report = std::sync::Arc::new(mk_quiet_report()); - } else if atty::is(Stream::Stdout) { - report = std::sync::Arc::new(mk_progress_bar_report()); - } else { - report = Arc::new(mk_simple_report()); - } - let opts = ThinRepairOptions { input: &input_file, output: &output_file, diff --git a/src/bin/thin_restore.rs b/src/commands/thin_restore.rs similarity index 64% rename from src/bin/thin_restore.rs rename to src/commands/thin_restore.rs index be471d8..b694304 100644 --- a/src/bin/thin_restore.rs +++ b/src/commands/thin_restore.rs @@ -1,18 +1,15 @@ extern crate clap; -extern crate thinp; -use atty::Stream; use clap::{App, Arg}; use std::path::Path; use std::process; -use std::sync::Arc; -use thinp::file_utils; -use thinp::report::*; -use thinp::thin::restore::{restore, ThinRestoreOptions}; -fn main() { +use crate::commands::utils::*; +use crate::thin::restore::{restore, ThinRestoreOptions}; + +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("thin_restore") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Convert XML format metadata to binary.") // flags .arg( @@ -45,29 +42,13 @@ fn main() { .required(true), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); let input_file = Path::new(matches.value_of("INPUT").unwrap()); let output_file = Path::new(matches.value_of("OUTPUT").unwrap()); - if let Err(e) = file_utils::is_file(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - process::exit(1); - } - - if let Err(e) = file_utils::check_output_file_requirements(output_file) { - eprintln!("{}", e); - process::exit(1); - } - - let report; - - if matches.is_present("QUIET") { - report = std::sync::Arc::new(mk_quiet_report()); - } else if atty::is(Stream::Stdout) { - report = std::sync::Arc::new(mk_progress_bar_report()); - } else { - report = Arc::new(mk_simple_report()); - } + let report = mk_report(matches.is_present("QUIET")); + check_input_file(input_file, &report); + check_output_file(output_file, &report); let opts = ThinRestoreOptions { input: &input_file, diff --git a/src/bin/thin_shrink.rs b/src/commands/thin_shrink.rs similarity index 85% rename from src/bin/thin_shrink.rs rename to src/commands/thin_shrink.rs index e61ed5b..5d04f85 100644 --- a/src/bin/thin_shrink.rs +++ b/src/commands/thin_shrink.rs @@ -3,16 +3,16 @@ // https://github.com/nkshirsagar/thinpool_shrink/blob/split_ranges/thin_shrink.py extern crate clap; -extern crate thinp; use clap::{App, Arg}; use std::path::Path; use std::process::exit; -use thinp::file_utils; -fn main() { +use crate::commands::utils::*; + +pub fn run(args: &[std::ffi::OsString]) { let parser = App::new("thin_shrink") - .version(thinp::version::tools_version()) + .version(crate::version::tools_version()) .about("Rewrite xml metadata and move data in an inactive pool.") .arg( Arg::with_name("INPUT") @@ -57,7 +57,7 @@ fn main() { .takes_value(true), ); - let matches = parser.get_matches(); + let matches = parser.get_matches_from(args); // FIXME: check these look like xml let input_file = Path::new(matches.value_of("INPUT").unwrap()); @@ -66,13 +66,11 @@ fn main() { let data_file = Path::new(matches.value_of("DATA").unwrap()); let do_copy = !matches.is_present("NOCOPY"); - if let Err(e) = file_utils::is_file_or_blk(input_file) { - eprintln!("Invalid input file '{}': {}.", input_file.display(), e); - exit(1); - } + let report = mk_report(false); + check_input_file(input_file, &report); if let Err(reason) = - thinp::shrink::toplevel::shrink(&input_file, &output_file, &data_file, size, do_copy) + crate::shrink::toplevel::shrink(&input_file, &output_file, &data_file, size, do_copy) { eprintln!("Application error: {}\n", reason); exit(1); diff --git a/src/commands/utils.rs b/src/commands/utils.rs new file mode 100644 index 0000000..4e5de06 --- /dev/null +++ b/src/commands/utils.rs @@ -0,0 +1,91 @@ +use anyhow::Result; +use atty::Stream; +use std::fs::OpenOptions; +use std::io::Read; +use std::path::Path; +use std::process::exit; + +use crate::file_utils; +use crate::report::*; + +pub fn check_input_file(input_file: &Path, report: &Report) { + if !file_utils::file_exists(input_file) { + report.fatal(&format!( + "Couldn't find input file '{:?}'.", + &input_file + )); + exit(1); + } + + if !file_utils::is_file_or_blk(input_file) { + report.fatal(&format!( + "Not a block device or regular file '{:?}'.", + &input_file + )); + exit(1); + } +} + +pub fn check_file_not_tiny(input_file: &Path, report: &Report) { + if file_utils::file_size(input_file).expect("couldn't get input size") < 4096 { + report.fatal("Metadata device/file too small. Is this binary metadata?"); + exit(1); + } +} + +pub fn check_output_file(path: &Path, report: &Report) { + // minimal thin metadata size is 10 blocks, with one device + match file_utils::file_size(path) { + Ok(size) => { + if size < 40960 { + report.fatal("Output file too small."); + exit(1); + } + } + Err(e) => { + report.fatal(&format!("{}", e)); + exit(1); + } + } +} + +pub fn mk_report(quiet: bool) -> std::sync::Arc { + use std::sync::Arc; + + if quiet { + Arc::new(mk_quiet_report()) + } else if atty::is(Stream::Stdout) { + Arc::new(mk_progress_bar_report()) + } else { + Arc::new(mk_simple_report()) + } +} + +fn is_xml(line: &[u8]) -> bool { + line.starts_with(b" Result<()> { + let mut file = OpenOptions::new() + .read(true) + .open(input_file)?; + let mut data = vec![0; 16]; + file.read_exact(&mut data)?; + + if is_xml(&data) { + report.fatal("This looks like XML. This tool only checks the binary metadata format."); + } + + Ok(()) +} + +/// This trys to read the start of input_path to see +/// if it's xml. If there are any problems reading the file +/// then it fails silently. +pub fn check_not_xml(input_file: &Path, report: &Report) { + let _ = check_not_xml_(input_file, report); +} + +//--------------------------------------- diff --git a/src/file_utils.rs b/src/file_utils.rs index fffc014..6c06819 100644 --- a/src/file_utils.rs +++ b/src/file_utils.rs @@ -1,5 +1,5 @@ use nix::sys::stat; -use nix::sys::stat::FileStat; +use nix::sys::stat::{FileStat, SFlag}; use std::fs::{File, OpenOptions}; use std::io; use std::io::{Seek, Write}; @@ -9,47 +9,38 @@ use tempfile::tempfile; //--------------------------------------- -#[inline(always)] -pub fn s_isreg(info: &FileStat) -> bool { - (info.st_mode & stat::SFlag::S_IFMT.bits()) == stat::SFlag::S_IFREG.bits() +fn test_bit(mode: u32, flag: SFlag) -> bool { + SFlag::from_bits_truncate(mode).contains(flag) } -#[inline(always)] -pub fn s_isblk(info: &FileStat) -> bool { - (info.st_mode & stat::SFlag::S_IFMT.bits()) == stat::SFlag::S_IFBLK.bits() +pub fn is_file_or_blk_(info: FileStat) -> bool { + test_bit(info.st_mode, SFlag::S_IFBLK) || test_bit(info.st_mode, SFlag::S_IFREG) } -pub fn is_file(path: &Path) -> io::Result<()> { +pub fn file_exists(path: &Path) -> bool { match stat::stat(path) { - Ok(info) => { - if s_isreg(&info) { - Ok(()) - } else { - fail("Not a regular file") - } - } - _ => { - // FIXME: assuming all errors indicate the file doesn't - // exist. - fail("No such file or directory") - } + Ok(_) => true, + _ => false, } } -pub fn is_file_or_blk(path: &Path) -> io::Result<()> { +pub fn is_file_or_blk(path: &Path) -> bool { + match stat::stat(path) { + Ok(info) =>is_file_or_blk_(info), + _ => false, + } +} + +pub fn is_file(path: &Path) -> bool { match stat::stat(path) { Ok(info) => { - if s_isreg(&info) || s_isblk(&info) { - Ok(()) + if test_bit(info.st_mode, SFlag::S_IFREG) { + true } else { - fail("Not a block device or regular file") + false } } - _ => { - // FIXME: assuming all errors indicate the file doesn't - // exist. - fail("No such file or directory") - } + _ => false, } } @@ -79,9 +70,9 @@ fn get_device_size(path: &Path) -> io::Result { pub fn file_size(path: &Path) -> io::Result { match stat::stat(path) { Ok(info) => { - if s_isreg(&info) { + if test_bit(info.st_mode, SFlag::S_IFREG) { Ok(info.st_size as u64) - } else if s_isblk(&info) { + } else if test_bit(info.st_mode, SFlag::S_IFBLK) { get_device_size(path) } else { fail("Not a block device or regular file") @@ -123,12 +114,4 @@ pub fn create_sized_file(path: &Path, nr_bytes: u64) -> io::Result io::Result<()> { - // minimal thin metadata size is 10 blocks, with one device - if file_size(path)? < 40960 { - return fail("Output file too small."); - } - Ok(()) -} -//--------------------------------------- diff --git a/src/lib.rs b/src/lib.rs index 8398379..0125836 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ extern crate quickcheck_macros; pub mod cache; pub mod checksum; +pub mod commands; pub mod file_utils; pub mod io_engine; pub mod math; diff --git a/src/report.rs b/src/report.rs index 6cfeb4b..8b0fb3e 100644 --- a/src/report.rs +++ b/src/report.rs @@ -34,6 +34,7 @@ pub trait ReportInner { fn set_sub_title(&mut self, txt: &str); fn progress(&mut self, percent: u8); fn log(&mut self, txt: &str); + fn to_stdout(&mut self, txt: &str); fn complete(&mut self); } @@ -91,6 +92,13 @@ impl Report { let outcome = self.outcome.lock().unwrap(); outcome.clone() } + + // Force a message to be printed to stdout. eg, + // TRANSACTION_ID = + pub fn to_stdout(&self, txt: &str) { + let mut inner = self.inner.lock().unwrap(); + inner.to_stdout(txt) + } } //------------------------------------------ @@ -126,6 +134,10 @@ impl ReportInner for PBInner { self.bar.println(txt); } + fn to_stdout(&mut self, txt: &str) { + println!("{}", txt); + } + fn complete(&mut self) { self.bar.finish(); } @@ -173,6 +185,10 @@ impl ReportInner for SimpleInner { eprintln!("{}", txt); } + fn to_stdout(&mut self, txt: &str) { + println!("{}", txt); + } + fn complete(&mut self) {} } @@ -192,6 +208,7 @@ impl ReportInner for QuietInner { fn progress(&mut self, _percent: u8) {} fn log(&mut self, _txt: &str) {} + fn to_stdout(&mut self, _txt: &str) {} fn complete(&mut self) {} } diff --git a/src/thin/check.rs b/src/thin/check.rs index e3e8a93..e8e45e6 100644 --- a/src/thin/check.rs +++ b/src/thin/check.rs @@ -218,7 +218,7 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { // superblock let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?; - report.info(&format!("TRANSACTION_ID={}", sb.transaction_id)); + report.to_stdout(&format!("TRANSACTION_ID={}", sb.transaction_id)); if opts.sb_only { return Ok(()); @@ -295,7 +295,7 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { report.set_sub_title("metadata space map"); let root = unpack::(&sb.metadata_sm_root[0..])?; - report.info(&format!( + report.to_stdout(&format!( "METADATA_FREE_BLOCKS={}", root.nr_blocks - root.nr_allocated )); diff --git a/tests/cache_check.rs b/tests/cache_check.rs index bdc2a72..45c1995 100644 --- a/tests/cache_check.rs +++ b/tests/cache_check.rs @@ -13,16 +13,24 @@ use common::test_dir::*; //------------------------------------------ -const USAGE: &str = "Usage: cache_check [options] {device|file}\n\ - Options:\n \ - {-q|--quiet}\n \ - {-h|--help}\n \ - {-V|--version}\n \ - {--clear-needs-check-flag}\n \ - {--super-block-only}\n \ - {--skip-mappings}\n \ - {--skip-hints}\n \ - {--skip-discards}"; +const USAGE: &str = +"cache_check 0.9.0 + +USAGE: + cache_check [FLAGS] + +FLAGS: + --auto-repair Auto repair trivial issues. + --ignore-non-fatal-errors Only return a non-zero exit code if a fatal error is found. + -q, --quiet Suppress output messages, return only exit code. + --super-block-only Only check the superblock. + --skip-discards Don't check the discard bitset + --skip-hints Don't check the hint array + -h, --help Prints help information + -V, --version Prints version information + +ARGS: + Specify the input device to check"; //------------------------------------------ @@ -33,8 +41,12 @@ impl<'a> Program<'a> for CacheCheck { "cache_check" } - fn path() -> &'a std::ffi::OsStr { - CACHE_CHECK.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + cache_check_cmd(args) } fn usage() -> &'a str { @@ -91,8 +103,9 @@ test_corrupted_input_data!(CacheCheck); fn failing_q() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; - let output = run_fail_raw(CACHE_CHECK, args!["-q", &md])?; + let output = run_fail_raw(cache_check_cmd(args!["-q", &md]))?; assert_eq!(output.stdout.len(), 0); + eprintln!("stderr = '{}'", std::str::from_utf8(&output.stderr).unwrap()); assert_eq!(output.stderr.len(), 0); Ok(()) } @@ -101,7 +114,7 @@ fn failing_q() -> Result<()> { fn failing_quiet() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; - let output = run_fail_raw(CACHE_CHECK, args!["--quiet", &md])?; + let output = run_fail_raw(cache_check_cmd(args!["--quiet", &md]))?; assert_eq!(output.stdout.len(), 0); assert_eq!(output.stderr.len(), 0); Ok(()) @@ -111,17 +124,23 @@ fn failing_quiet() -> Result<()> { fn valid_metadata_passes() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_ok(CACHE_CHECK, args![&md])?; + run_ok(cache_check_cmd(args![&md]))?; Ok(()) } + +// FIXME: put back in, I don't want to add the --debug- arg to the +// tool again, so we should have a little library function for tweaking +// metadata version. + +/* #[test] fn bad_metadata_version() -> Result<()> { let mut td = TestDir::new()?; let xml = mk_valid_xml(&mut td)?; let md = mk_zeroed_md(&mut td)?; run_ok( - CACHE_RESTORE, + cache_restore_cmd( args![ "-i", &xml, @@ -130,7 +149,8 @@ fn bad_metadata_version() -> Result<()> { "--debug-override-metadata-version", "12345" ], - )?; - run_fail(CACHE_CHECK, args![&md])?; + ))?; + run_fail(cache_check_cmd(args![&md]))?; Ok(()) } +*/ diff --git a/tests/cache_dump.rs b/tests/cache_dump.rs index 4d44dcd..63c5933 100644 --- a/tests/cache_dump.rs +++ b/tests/cache_dump.rs @@ -15,12 +15,22 @@ use common::test_dir::*; //------------------------------------------ -const USAGE: &str = "Usage: cache_dump [options] {device|file}\n\ - Options:\n \ - {-h|--help}\n \ - {-o }\n \ - {-V|--version}\n \ - {--repair}"; +const USAGE: &str = "cache_dump 0.9.0 +Dump the cache metadata to stdout in XML format + +USAGE: + cache_dump [FLAGS] [OPTIONS] + +FLAGS: + -r, --repair Repair the metadata whilst dumping it + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + -o, --output Specify the output file rather than stdout + +ARGS: + Specify the input device to dump"; //------------------------------------------ @@ -31,8 +41,12 @@ impl<'a> Program<'a> for CacheDump { "cache_dump" } - fn path() -> &'a std::ffi::OsStr { - CACHE_DUMP.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + cache_dump_cmd(args) } fn usage() -> &'a str { @@ -84,7 +98,7 @@ test_unreadable_input_file!(CacheDump); fn dump_restore_cycle() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let output = run_ok_raw(CACHE_DUMP, args![&md])?; + let output = run_ok_raw(cache_dump_cmd(args![&md]))?; let xml = td.mk_path("meta.xml"); let mut file = OpenOptions::new() @@ -96,9 +110,9 @@ fn dump_restore_cycle() -> Result<()> { drop(file); let md2 = mk_zeroed_md(&mut td)?; - run_ok(CACHE_RESTORE, args!["-i", &xml, "-o", &md2])?; + run_ok(cache_restore_cmd(args!["-i", &xml, "-o", &md2]))?; - let output2 = run_ok_raw(CACHE_DUMP, args![&md2])?; + let output2 = run_ok_raw(cache_dump_cmd(args![&md2]))?; assert_eq!(output.stdout, output2.stdout); Ok(()) diff --git a/tests/cache_repair.rs b/tests/cache_repair.rs index c7701dd..95c08cc 100644 --- a/tests/cache_repair.rs +++ b/tests/cache_repair.rs @@ -12,12 +12,21 @@ use common::test_dir::*; //------------------------------------------ -const USAGE: &str = "Usage: cache_repair [options] {device|file}\n\ - Options:\n \ - {-h|--help}\n \ - {-i|--input} \n \ - {-o|--output} \n \ - {-V|--version}"; +const USAGE: &str = +"cache_repair 0.9.0 +Repair binary cache metadata, and write it to a different device or file + +USAGE: + cache_repair [FLAGS] --input --output + +FLAGS: + -q, --quiet Suppress output messages, return only exit code. + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + -i, --input Specify the input device + -o, --output Specify the output device"; //----------------------------------------- @@ -28,8 +37,12 @@ impl<'a> Program<'a> for CacheRepair { "cache_repair" } - fn path() -> &'a std::ffi::OsStr { - CACHE_REPAIR.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + cache_repair_cmd(args) } fn usage() -> &'a str { diff --git a/tests/cache_restore.rs b/tests/cache_restore.rs index 41c133a..4eaa51c 100644 --- a/tests/cache_restore.rs +++ b/tests/cache_restore.rs @@ -14,17 +14,21 @@ use common::test_dir::*; //------------------------------------------ -const USAGE: &str = "Usage: cache_restore [options]\n\ - Options:\n \ - {-h|--help}\n \ - {-i|--input} \n \ - {-o|--output} \n \ - {-q|--quiet}\n \ - {--metadata-version} <1 or 2>\n \ - {-V|--version}\n\ - \n \ - {--debug-override-metadata-version} \n \ - {--omit-clean-shutdown}"; +const USAGE: &str = +"cache_restore 0.9.0 +Convert XML format metadata to binary. + +USAGE: + cache_restore [FLAGS] --input --output + +FLAGS: + -q, --quiet Suppress output messages, return only exit code. + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + -i, --input Specify the input xml + -o, --output Specify the output device to check"; //------------------------------------------ @@ -35,8 +39,12 @@ impl<'a> Program<'a> for CacheRestore { "thin_restore" } - fn path() -> &'a std::ffi::OsStr { - CACHE_RESTORE.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + cache_restore_cmd(args) } fn usage() -> &'a str { @@ -105,7 +113,7 @@ fn quiet_flag(flag: &str) -> Result<()> { let xml = mk_valid_xml(&mut td)?; let md = mk_zeroed_md(&mut td)?; - let output = run_ok_raw(CACHE_RESTORE, args!["-i", &xml, "-o", &md, flag])?; + let output = run_ok_raw(cache_restore_cmd(args!["-i", &xml, "-o", &md, flag]))?; assert_eq!(output.stdout.len(), 0); assert_eq!(output.stderr.len(), 0); @@ -129,17 +137,19 @@ fn successfully_restores() -> Result<()> { let mut td = TestDir::new()?; let xml = mk_valid_xml(&mut td)?; let md = mk_zeroed_md(&mut td)?; - run_ok(CACHE_RESTORE, args!["-i", &xml, "-o", &md])?; + run_ok(cache_restore_cmd(args!["-i", &xml, "-o", &md]))?; Ok(()) } +// FIXME: finish +/* #[test] fn override_metadata_version() -> Result<()> { let mut td = TestDir::new()?; let xml = mk_valid_xml(&mut td)?; let md = mk_zeroed_md(&mut td)?; run_ok( - CACHE_RESTORE, + cache_restore_cmd( args![ "-i", &xml, @@ -148,20 +158,24 @@ fn override_metadata_version() -> Result<()> { "--debug-override-metadata-version", "10298" ], - )?; + ))?; Ok(()) } +*/ +// FIXME: finish +/* #[test] fn accepts_omit_clean_shutdown() -> Result<()> { let mut td = TestDir::new()?; let xml = mk_valid_xml(&mut td)?; let md = mk_zeroed_md(&mut td)?; run_ok( - CACHE_RESTORE, + cache_restore_cmd( args!["-i", &xml, "-o", &md, "--omit-clean-shutdown"], - )?; + ))?; Ok(()) } +*/ //----------------------------------------- diff --git a/tests/common/cache.rs b/tests/common/cache.rs index 449f00f..412d773 100644 --- a/tests/common/cache.rs +++ b/tests/common/cache.rs @@ -27,7 +27,7 @@ pub fn mk_valid_md(td: &mut TestDir) -> Result { write_xml(&xml, &mut gen)?; let _file = file_utils::create_sized_file(&md, 4096 * 4096); - run_ok(CACHE_RESTORE, args!["-i", &xml, "-o", &md])?; + run_ok(cache_restore_cmd(args!["-i", &xml, "-o", &md]))?; Ok(md) } diff --git a/tests/common/common_args.rs b/tests/common/common_args.rs index a0cba34..52b01d4 100644 --- a/tests/common/common_args.rs +++ b/tests/common/common_args.rs @@ -13,7 +13,7 @@ pub fn test_help_short<'a, P>() -> Result<()> where P: Program<'a>, { - let stdout = run_ok(P::path(), args!["-h"])?; + let stdout = run_ok(P::cmd(args!["-h"]))?; assert_eq!(stdout, P::usage()); Ok(()) } @@ -22,7 +22,7 @@ pub fn test_help_long<'a, P>() -> Result<()> where P: Program<'a>, { - let stdout = run_ok(P::path(), args!["--help"])?; + let stdout = run_ok(P::cmd(vec!["--help"]))?; assert_eq!(stdout, P::usage()); Ok(()) } @@ -49,7 +49,7 @@ pub fn test_version_short<'a, P>() -> Result<()> where P: Program<'a>, { - let stdout = run_ok(P::path(), args!["-V"])?; + let stdout = run_ok(P::cmd(args!["-V"]))?; assert!(stdout.contains(tools_version())); Ok(()) } @@ -58,7 +58,7 @@ pub fn test_version_long<'a, P>() -> Result<()> where P: Program<'a>, { - let stdout = run_ok(P::path(), args!["--version"])?; + let stdout = run_ok(P::cmd(args!["--version"]))?; assert!(stdout.contains(tools_version())); Ok(()) } @@ -85,7 +85,7 @@ where P: Program<'a>, { let option = "--hedgehogs-only"; - let stderr = run_fail(P::path(), args![option])?; + let stderr = run_fail(P::cmd(args![option]))?; assert!(stderr.contains(&P::bad_option_hint(option))); Ok(()) } diff --git a/tests/common/input_arg.rs b/tests/common/input_arg.rs index 7ef7e69..87b1673 100644 --- a/tests/common/input_arg.rs +++ b/tests/common/input_arg.rs @@ -63,7 +63,7 @@ where P: InputProgram<'a>, { let args: [&str; 0] = []; - let stderr = run_fail(P::path(), args)?; + let stderr = run_fail(P::cmd(args))?; assert!(stderr.contains(P::missing_input_arg())); Ok(()) } @@ -85,7 +85,7 @@ where let mut td = TestDir::new()?; let output = mk_zeroed_md(&mut td)?; ensure_untouched(&output, || { - let stderr = run_fail(P::path(), args!["-o", &output])?; + let stderr = run_fail(P::cmd(args!["-o", &output]))?; assert!(stderr.contains(P::missing_input_arg())); Ok(()) }) @@ -109,7 +109,7 @@ where let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, "no-such-file".as_ref(), &|args: &[&OsStr]| { - let stderr = run_fail(P::path(), args)?; + let stderr = run_fail(P::cmd(args))?; assert!(stderr.contains(P::file_not_found())); Ok(()) }) @@ -133,7 +133,7 @@ where let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, "/tmp".as_ref(), &|args: &[&OsStr]| { - let stderr = run_fail(P::path(), args)?; + let stderr = run_fail(P::cmd(args))?; assert!(stderr.contains("Not a block device or regular file")); Ok(()) }) @@ -161,7 +161,7 @@ where let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, input.as_ref(), &|args: &[&OsStr]| { - let stderr = run_fail(P::path(), args)?; + let stderr = run_fail(P::cmd(args))?; assert!(stderr.contains("Permission denied")); Ok(()) }) @@ -191,7 +191,8 @@ where let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, input.as_ref(), &|args: &[&OsStr]| { - let stderr = run_fail(P::path(), args)?; + let stderr = run_fail(P::cmd(args))?; + eprintln!("actual: {:?}", stderr); assert!(stderr.contains("Metadata device/file too small. Is this binary metadata?")); Ok(()) }) @@ -220,11 +221,9 @@ where let wrapper = build_args_fn(P::arg_type())?; wrapper(&mut td, input.as_ref(), &|args: &[&OsStr]| { - let stderr = run_fail(P::path(), args)?; - eprintln!("{}", stderr); + let stderr = run_fail(P::cmd(args))?; let msg = format!( - "This looks like XML. {} only checks the binary metadata format.", - P::name() + "This looks like XML. This tool only checks the binary metadata format.", ); assert!(stderr.contains(&msg)); Ok(()) @@ -253,7 +252,7 @@ where ArgType::IoOptions => with_output_superblock_zeroed, }; wrapper(&mut td, input.as_ref(), &|args: &[&OsStr]| { - let stderr = run_fail(P::path(), args)?; + let stderr = run_fail(P::cmd(args))?; assert!(stderr.contains(P::corrupted_input())); Ok(()) }) diff --git a/tests/common/output_option.rs b/tests/common/output_option.rs index 5a627c4..dad9e30 100644 --- a/tests/common/output_option.rs +++ b/tests/common/output_option.rs @@ -16,7 +16,7 @@ where { let mut td = TestDir::new()?; let input = P::mk_valid_input(&mut td)?; - let stderr = run_fail(P::path(), args!["-i", &input])?; + let stderr = run_fail(P::cmd(args!["-i", &input]))?; assert!(stderr.contains(P::missing_output_arg())); Ok(()) } @@ -37,7 +37,9 @@ where { let mut td = TestDir::new()?; let input = P::mk_valid_input(&mut td)?; - let stderr = run_fail(P::path(), args!["-i", &input, "-o", "no-such-file"])?; + let cmd = P::cmd(args!["-i", &input, "-o", "no-such-file"]); + let stderr = run_fail(cmd)?; + assert!(stderr.contains(

::file_not_found())); Ok(()) } @@ -58,7 +60,7 @@ where { let mut td = TestDir::new()?; let input = P::mk_valid_input(&mut td)?; - let stderr = run_fail(P::path(), args!["-i", &input, "-o", "/tmp"])?; + let stderr = run_fail(P::cmd(args!["-i", &input, "-o", "/tmp"]))?; assert!(stderr.contains("Not a block device or regular file")); Ok(()) } @@ -84,7 +86,7 @@ where let _file = file_utils::create_sized_file(&output, 4_194_304); duct::cmd!("chmod", "-w", &output).run()?; - let stderr = run_fail(P::path(), args!["-i", &input, "-o", &output])?; + let stderr = run_fail(P::cmd(args!["-i", &input, "-o", &output]))?; assert!(stderr.contains("Permission denied")); Ok(()) } @@ -113,7 +115,7 @@ where let output = td.mk_path("meta.bin"); let _file = file_utils::create_sized_file(&output, 4096); - let stderr = run_fail(P::path(), args!["-i", &input, "-o", &output])?; + let stderr = run_fail(P::cmd(args!["-i", &input, "-o", &output]))?; assert!(stderr.contains("Output file too small")); Ok(()) } diff --git a/tests/common/process.rs b/tests/common/process.rs index a5da6d2..2d74d1c 100644 --- a/tests/common/process.rs +++ b/tests/common/process.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use std::ffi::{OsStr, OsString}; //------------------------------------------ @@ -15,49 +14,29 @@ macro_rules! args { } // 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(); +pub fn run_ok(command: duct::Expression) -> Result { + let command = command.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(); +pub fn run_ok_raw(command: duct::Expression) -> Result { + let command = command.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(); +pub fn run_fail(command: duct::Expression) -> Result { + let command = command.stdout_capture().stderr_capture(); let output = command.unchecked().run()?; assert!(!output.status.success()); let stderr = std::str::from_utf8(&output.stderr[..]).unwrap().to_string(); @@ -65,15 +44,8 @@ where } // 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(); +pub fn run_fail_raw(command: duct::Expression) -> Result { + let command = command.stdout_capture().stderr_capture(); let output = command.unchecked().run()?; assert!(!output.status.success()); Ok(output) diff --git a/tests/common/program.rs b/tests/common/program.rs index 0414b3d..fd15639 100644 --- a/tests/common/program.rs +++ b/tests/common/program.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use std::ffi::OsStr; use std::path::PathBuf; use crate::common::test_dir::TestDir; @@ -13,7 +12,10 @@ pub enum ArgType { pub trait Program<'a> { fn name() -> &'a str; - fn path() -> &'a OsStr; + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into; fn usage() -> &'a str; fn arg_type() -> ArgType; diff --git a/tests/common/target.rs b/tests/common/target.rs index d30eb28..965f94e 100644 --- a/tests/common/target.rs +++ b/tests/common/target.rs @@ -1,69 +1,159 @@ -//------------------------------------------ - -#[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) - }; -} +use std::ffi::OsString; +use std::path::PathBuf; //------------------------------------------ -pub const CACHE_CHECK: &str = path_to!("cache_check"); -pub const CACHE_DUMP: &str = path_to!("cache_dump"); -pub const CACHE_REPAIR: &str = path_to!("cache_repair"); -pub const CACHE_RESTORE: &str = path_to!("cache_restore"); +pub fn cpp_cmd(cmd: S, args: I) -> duct::Expression +where + S: Into, + I: IntoIterator, + I::Item: Into, +{ + let mut bin = PathBuf::from("./bin"); + bin.push(Into::::into(cmd)); + duct::cmd(bin.as_path(), args) +} -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!("thin_repair"); -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 fn rust_cmd(cmd: S, args: I) -> duct::Expression +where + S: Into, + I: IntoIterator, + I::Item: Into, +{ + const RUST_PATH: &str = env!(concat!("CARGO_BIN_EXE_", "pdata_tools")); -//------------------------------------------ - -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) + let mut all_args = vec![Into::::into(cmd)]; + for a in args { + all_args.push(Into::::into(a)); } + + duct::cmd(RUST_PATH, &all_args) } -pub mod rust_msg { - pub const FILE_NOT_FOUND: &str = "No such file or directory"; +pub fn thin_check_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("thin_check", args) +} + +pub fn thin_rmap_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + cpp_cmd("thin_rmap", args) +} + +pub fn thin_generate_metadata_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + cpp_cmd("thin_generate_metadata", args) +} + +pub fn thin_generate_mappings_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + cpp_cmd("thin_generate_mappings", args) +} + +pub fn thin_generate_damage_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + cpp_cmd("thin_generate_damage", args) +} + +pub fn thin_restore_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("thin_restore", args) +} + +pub fn thin_repair_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("thin_restore", args) +} + +pub fn thin_dump_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("thin_dump", args) +} + +pub fn thin_delta_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + cpp_cmd("thin_delta", args) +} + +pub fn thin_metadata_pack_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("thin_metadata_pack", args) +} + +pub fn thin_metadata_unpack_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("thin_metadata_unpack", args) +} + +pub fn cache_check_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("cache_check", args) +} + +pub fn cache_dump_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("cache_dump", args) +} + +pub fn cache_restore_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("cache_restore", args) +} + +pub fn cache_repair_cmd(args: I) -> duct::Expression +where + I: IntoIterator, + I::Item: Into, +{ + rust_cmd("cache_repair", args) +} + +//------------------------------------------ + +pub mod 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"; @@ -73,9 +163,4 @@ pub mod rust_msg { } } -#[cfg(not(feature = "rust_tests"))] -pub use cpp_msg as msg; -#[cfg(feature = "rust_tests")] -pub use rust_msg as msg; - //------------------------------------------ diff --git a/tests/common/thin.rs b/tests/common/thin.rs index a098ae2..a66ece7 100644 --- a/tests/common/thin.rs +++ b/tests/common/thin.rs @@ -28,7 +28,7 @@ pub fn mk_valid_md(td: &mut TestDir) -> Result { write_xml(&xml, &mut gen)?; let _file = file_utils::create_sized_file(&md, 4096 * 4096); - run_ok(THIN_RESTORE, args!["-i", &xml, "-o", &md])?; + run_ok(thin_restore_cmd(args!["-i", &xml, "-o", &md]))?; Ok(md) } @@ -39,11 +39,11 @@ pub fn mk_valid_md(td: &mut TestDir) -> Result { pub fn prep_metadata(td: &mut TestDir) -> Result { let md = mk_zeroed_md(td)?; let args = args!["-o", &md, "--format", "--nr-data-blocks", "102400"]; - run_ok(THIN_GENERATE_METADATA, args)?; + run_ok(thin_generate_metadata_cmd(args))?; // Create a 2GB device let args = args!["-o", &md, "--create-thin", "1"]; - run_ok(THIN_GENERATE_METADATA, args)?; + run_ok(thin_generate_metadata_cmd(args))?; let args = args![ "-o", &md, @@ -54,7 +54,7 @@ pub fn prep_metadata(td: &mut TestDir) -> Result { "--rw=randwrite", "--seq-nr=16" ]; - run_ok(THIN_GENERATE_MAPPINGS, args)?; + run_ok(thin_generate_mappings_cmd(args))?; // Take a few snapshots. let mut snap_id = 2; @@ -62,7 +62,7 @@ pub fn prep_metadata(td: &mut TestDir) -> Result { // take a snapshot let snap_id_str = snap_id.to_string(); let args = args!["-o", &md, "--create-snap", &snap_id_str, "--origin", "1"]; - run_ok(THIN_GENERATE_METADATA, args)?; + run_ok(thin_generate_metadata_cmd(args))?; // partially overwrite the origin (64MB) let args = args![ @@ -77,7 +77,7 @@ pub fn prep_metadata(td: &mut TestDir) -> Result { "--rw=randwrite", "--seq-nr=16" ]; - run_ok(THIN_GENERATE_MAPPINGS, args)?; + run_ok(thin_generate_mappings_cmd(args))?; snap_id += 1; } @@ -86,7 +86,7 @@ pub fn prep_metadata(td: &mut TestDir) -> Result { pub fn set_needs_check(md: &PathBuf) -> Result<()> { let args = args!["-o", &md, "--set-needs-check"]; - run_ok(THIN_GENERATE_METADATA, args)?; + run_ok(thin_generate_metadata_cmd(args))?; Ok(()) } @@ -110,7 +110,7 @@ pub fn generate_metadata_leaks( "--actual", &actual_str ]; - run_ok(THIN_GENERATE_DAMAGE, args)?; + run_ok(thin_generate_damage_cmd(args))?; Ok(()) } diff --git a/tests/thin_check.rs b/tests/thin_check.rs index 608a5cb..d89e497 100644 --- a/tests/thin_check.rs +++ b/tests/thin_check.rs @@ -13,18 +13,28 @@ use common::thin::*; //------------------------------------------ -const USAGE: &str = "Usage: thin_check [options] {device|file}\n\ - Options:\n \ - {-q|--quiet}\n \ - {-h|--help}\n \ - {-V|--version}\n \ - {-m|--metadata-snap}\n \ - {--auto-repair}\n \ - {--override-mapping-root}\n \ - {--clear-needs-check-flag}\n \ - {--ignore-non-fatal-errors}\n \ - {--skip-mappings}\n \ - {--super-block-only}"; +const USAGE: &str = "thin_check 0.9.0 +Validates thin provisioning metadata on a device or file. + +USAGE: + thin_check [FLAGS] [OPTIONS] + +FLAGS: + --auto-repair Auto repair trivial issues. + --clear-needs-check-flag Clears the 'needs_check' flag in the superblock + --ignore-non-fatal-errors Only return a non-zero exit code if a fatal error is found. + -m, --metadata-snapshot Check the metadata snapshot on a live pool + -q, --quiet Suppress output messages, return only exit code. + --super-block-only Only check the superblock. + --skip-mappings Don't check the mapping tree + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + --override-mapping-root Specify a mapping root to use + +ARGS: + Specify the input device to check"; //----------------------------------------- @@ -35,8 +45,12 @@ impl<'a> Program<'a> for ThinCheck { "thin_check" } - fn path() -> &'a std::ffi::OsStr { - THIN_CHECK.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + thin_check_cmd(args) } fn usage() -> &'a str { @@ -93,7 +107,7 @@ test_corrupted_input_data!(ThinCheck); fn accepts_flag(flag: &str) -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_ok(THIN_CHECK, args![flag, &md])?; + run_ok(thin_check_cmd(args![flag, &md]))?; Ok(()) } @@ -130,7 +144,14 @@ fn accepts_quiet() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let output = run_ok_raw(THIN_CHECK, args!["--quiet", &md])?; + let output = run_ok_raw(thin_check_cmd(args!["--quiet", &md]))?; + if output.stdout.len() > 0 { + eprintln!("stdout: {:?}", &std::str::from_utf8(&output.stdout)); + } + + if output.stderr.len() > 0 { + eprintln!("stderr: {:?}", &std::str::from_utf8(&output.stderr)); + } assert_eq!(output.stdout.len(), 0); assert_eq!(output.stderr.len(), 0); Ok(()) @@ -143,7 +164,7 @@ fn accepts_quiet() -> Result<()> { fn detects_corrupt_superblock_with_superblock_only() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; - let _stderr = run_fail(THIN_CHECK, args!["--super-block-only", &md])?; + let _stderr = run_fail(thin_check_cmd(args!["--super-block-only", &md]))?; Ok(()) } @@ -154,7 +175,8 @@ fn detects_corrupt_superblock_with_superblock_only() -> Result<()> { fn prints_info_fields() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let stdout = run_ok(THIN_CHECK, args![&md])?; + let stdout = run_ok(thin_check_cmd(args![&md]))?; + eprintln!("info: {:?}", stdout); assert!(stdout.contains("TRANSACTION_ID=")); assert!(stdout.contains("METADATA_FREE_BLOCKS=")); Ok(()) @@ -167,20 +189,28 @@ fn prints_info_fields() -> Result<()> { fn auto_repair_incompatible_opts() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_fail(THIN_CHECK, args!["--auto-repair", "-m", &md])?; - run_fail( - THIN_CHECK, - args!["--auto-repair", "--override-mapping-root", "123", &md], - )?; - run_fail( - THIN_CHECK, - args!["--auto-repair", "--super-block-only", &md], - )?; - run_fail(THIN_CHECK, args!["--auto-repair", "--skip-mappings", &md])?; - run_fail( - THIN_CHECK, - args!["--auto-repair", "--ignore-non-fatal-errors", &md], - )?; + run_fail(thin_check_cmd(args!["--auto-repair", "-m", &md]))?; + run_fail(thin_check_cmd(args![ + "--auto-repair", + "--override-mapping-root", + "123", + &md + ]))?; + run_fail(thin_check_cmd(args![ + "--auto-repair", + "--super-block-only", + &md + ]))?; + run_fail(thin_check_cmd(args![ + "--auto-repair", + "--skip-mappings", + &md + ]))?; + run_fail(thin_check_cmd(args![ + "--auto-repair", + "--ignore-non-fatal-errors", + &md + ]))?; Ok(()) } @@ -188,24 +218,23 @@ fn auto_repair_incompatible_opts() -> Result<()> { fn clear_needs_check_incompatible_opts() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_fail(THIN_CHECK, args!["--clear-needs-check-flag", "-m", &md])?; - run_fail( - THIN_CHECK, - args![ - "--clear-needs-check-flag", - "--override-mapping-root", - "123", - &md - ], - )?; - run_fail( - THIN_CHECK, - args!["--clear-needs-check-flag", "--super-block-only", &md], - )?; - run_fail( - THIN_CHECK, - args!["--clear-needs-check-flag", "--ignore-non-fatal-errors", &md], - )?; + run_fail(thin_check_cmd(args!["--clear-needs-check-flag", "-m", &md]))?; + run_fail(thin_check_cmd(args![ + "--clear-needs-check-flag", + "--override-mapping-root", + "123", + &md + ]))?; + run_fail(thin_check_cmd(args![ + "--clear-needs-check-flag", + "--super-block-only", + &md + ]))?; + run_fail(thin_check_cmd(args![ + "--clear-needs-check-flag", + "--ignore-non-fatal-errors", + &md + ]))?; Ok(()) } @@ -220,7 +249,7 @@ fn clear_needs_check() -> Result<()> { set_needs_check(&md)?; assert!(get_needs_check(&md)?); - run_ok(THIN_CHECK, args!["--clear-needs-check-flag", &md])?; + run_ok(thin_check_cmd(args!["--clear-needs-check-flag", &md]))?; assert!(!get_needs_check(&md)?); Ok(()) } @@ -232,7 +261,7 @@ fn no_clear_needs_check_if_error() -> Result<()> { set_needs_check(&md)?; generate_metadata_leaks(&md, 1, 0, 1)?; - run_fail(THIN_CHECK, args!["--clear-needs-check-flag", &md])?; + run_fail(thin_check_cmd(args!["--clear-needs-check-flag", &md]))?; assert!(get_needs_check(&md)?); Ok(()) } @@ -246,10 +275,11 @@ fn clear_needs_check_if_skip_mappings() -> Result<()> { generate_metadata_leaks(&md, 1, 0, 1)?; assert!(get_needs_check(&md)?); - run_ok( - THIN_CHECK, - args!["--clear-needs-check-flag", "--skip-mappings", &md], - )?; + run_ok(thin_check_cmd(args![ + "--clear-needs-check-flag", + "--skip-mappings", + &md + ]))?; assert!(!get_needs_check(&md)?); Ok(()) } @@ -262,8 +292,8 @@ fn metadata_leaks_are_non_fatal() -> Result<()> { let mut td = TestDir::new()?; let md = prep_metadata(&mut td)?; generate_metadata_leaks(&md, 1, 0, 1)?; - run_fail(THIN_CHECK, args![&md])?; - run_ok(THIN_CHECK, args!["--ignore-non-fatal-errors", &md])?; + run_fail(thin_check_cmd(args![&md]))?; + run_ok(thin_check_cmd(args!["--ignore-non-fatal-errors", &md]))?; Ok(()) } @@ -274,7 +304,7 @@ fn fatal_errors_cant_be_ignored() -> Result<()> { generate_metadata_leaks(&md, 1, 1, 0)?; ensure_untouched(&md, || { - run_fail(THIN_CHECK, args!["--ignore-non-fatal-errors", &md])?; + run_fail(thin_check_cmd(args!["--ignore-non-fatal-errors", &md]))?; Ok(()) }) } @@ -289,14 +319,14 @@ fn auto_repair() -> Result<()> { // auto-repair should have no effect on good metadata. ensure_untouched(&md, || { - run_ok(THIN_CHECK, args!["--auto-repair", &md])?; + run_ok(thin_check_cmd(args!["--auto-repair", &md]))?; Ok(()) })?; generate_metadata_leaks(&md, 16, 0, 1)?; - run_fail(THIN_CHECK, args![&md])?; - run_ok(THIN_CHECK, args!["--auto-repair", &md])?; - run_ok(THIN_CHECK, args![&md])?; + run_fail(thin_check_cmd(args![&md]))?; + run_ok(thin_check_cmd(args!["--auto-repair", &md]))?; + run_ok(thin_check_cmd(args![&md]))?; Ok(()) } @@ -307,7 +337,7 @@ fn auto_repair_has_limits() -> Result<()> { generate_metadata_leaks(&md, 16, 1, 0)?; ensure_untouched(&md, || { - run_fail(THIN_CHECK, args!["--auto-repair", &md])?; + run_fail(thin_check_cmd(args!["--auto-repair", &md]))?; Ok(()) })?; Ok(()) @@ -319,7 +349,7 @@ fn auto_repair_clears_needs_check() -> Result<()> { let md = prep_metadata(&mut td)?; set_needs_check(&md)?; - run_ok(THIN_CHECK, args!["--auto-repair", &md])?; + run_ok(thin_check_cmd(args!["--auto-repair", &md]))?; assert!(!get_needs_check(&md)?); Ok(()) } diff --git a/tests/thin_delta.rs b/tests/thin_delta.rs index a47eacb..4b9cba0 100644 --- a/tests/thin_delta.rs +++ b/tests/thin_delta.rs @@ -29,8 +29,12 @@ impl<'a> Program<'a> for ThinDelta { "thin_delta" } - fn path() -> &'a std::ffi::OsStr { - THIN_DELTA.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + rust_cmd("thin_delta", args) } fn usage() -> &'a str { @@ -42,7 +46,7 @@ impl<'a> Program<'a> for ThinDelta { } fn bad_option_hint(option: &str) -> String { - cpp_msg::bad_option_hint(option) + msg::bad_option_hint(option) } } @@ -58,7 +62,7 @@ test_rejects_bad_option!(ThinDelta); fn snap1_unspecified() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let stderr = run_fail(THIN_DELTA, args!["--snap2", "45", &md])?; + let stderr = run_fail(thin_delta_cmd(args!["--snap2", "45", &md]))?; assert!(stderr.contains("--snap1 or --root1 not specified")); Ok(()) } @@ -67,14 +71,14 @@ fn snap1_unspecified() -> Result<()> { fn snap2_unspecified() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let stderr = run_fail(THIN_DELTA, args!["--snap1", "45", &md])?; + let stderr = run_fail(thin_delta_cmd(args!["--snap1", "45", &md]))?; assert!(stderr.contains("--snap2 or --root2 not specified")); Ok(()) } #[test] fn dev_unspecified() -> Result<()> { - let stderr = run_fail(THIN_DELTA, args!["--snap1", "45", "--snap2", "46"])?; + let stderr = run_fail(thin_delta_cmd(args!["--snap1", "45", "--snap2", "46"]))?; // TODO: replace with msg::MISSING_INPUT_ARG once the rust version is ready assert!(stderr.contains("No input file provided")); Ok(()) diff --git a/tests/thin_dump.rs b/tests/thin_dump.rs index 206acfb..1ce6926 100644 --- a/tests/thin_dump.rs +++ b/tests/thin_dump.rs @@ -35,8 +35,12 @@ impl<'a> Program<'a> for ThinDump { "thin_dump" } - fn path() -> &'a std::ffi::OsStr { - THIN_DUMP.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + thin_dump_cmd(args) } fn usage() -> &'a str { @@ -89,7 +93,7 @@ fn dump_restore_cycle() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let output = run_ok_raw(THIN_DUMP, args![&md])?; + let output = run_ok_raw(thin_dump_cmd(args![&md]))?; let xml = td.mk_path("meta.xml"); let mut file = OpenOptions::new() @@ -101,9 +105,9 @@ fn dump_restore_cycle() -> Result<()> { drop(file); let md2 = mk_zeroed_md(&mut td)?; - run_ok(THIN_RESTORE, args!["-i", &xml, "-o", &md2])?; + run_ok(thin_restore_cmd(args!["-i", &xml, "-o", &md2]))?; - let output2 = run_ok_raw(THIN_DUMP, args![&md2])?; + let output2 = run_ok_raw(thin_dump_cmd(args![&md2]))?; assert_eq!(output.stdout, output2.stdout); Ok(()) @@ -118,7 +122,7 @@ fn no_stderr() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let output = run_ok_raw(THIN_DUMP, args![&md])?; + let output = run_ok_raw(thin_dump_cmd(args![&md]))?; assert_eq!(output.stderr.len(), 0); Ok(()) @@ -132,7 +136,7 @@ fn no_stderr() -> Result<()> { fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let output = run_ok_raw(THIN_DUMP, args![&md, flag, value])?; + let output = run_ok_raw(thin_dump_cmd(args![&md, flag, value]))?; if !cfg!(feature = "rust_tests") { assert_eq!(output.stderr.len(), 0); @@ -164,11 +168,10 @@ fn override_nr_data_blocks() -> Result<()> { fn repair_superblock() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let before = run_ok_raw(THIN_DUMP, args![&md])?; + let before = run_ok_raw(thin_dump_cmd(args![&md]))?; damage_superblock(&md)?; - let after = run_ok_raw( - THIN_DUMP, + let after = run_ok_raw(thin_dump_cmd( args![ "--repair", "--transaction-id=1", @@ -176,7 +179,7 @@ fn repair_superblock() -> Result<()> { "--nr-data-blocks=20480", &md ], - )?; + ))?; if !cfg!(feature = "rust_tests") { assert_eq!(after.stderr.len(), 0); } @@ -196,14 +199,14 @@ fn missing_transaction_id() -> Result<()> { let md = mk_valid_md(&mut td)?; damage_superblock(&md)?; let stderr = run_fail( - THIN_DUMP, + thin_dump_cmd( args![ "--repair", "--data-block-size=128", "--nr-data-blocks=20480", &md ], - )?; + ))?; assert!(stderr.contains("transaction id")); Ok(()) } @@ -214,14 +217,14 @@ fn missing_data_block_size() -> Result<()> { let md = mk_valid_md(&mut td)?; damage_superblock(&md)?; let stderr = run_fail( - THIN_DUMP, + thin_dump_cmd( args![ "--repair", "--transaction-id=1", "--nr-data-blocks=20480", &md ], - )?; + ))?; assert!(stderr.contains("data block size")); Ok(()) } @@ -233,14 +236,14 @@ fn missing_nr_data_blocks() -> Result<()> { let md = mk_valid_md(&mut td)?; damage_superblock(&md)?; let stderr = run_fail( - THIN_DUMP, + thin_dump_cmd( args![ "--repair", "--transaction-id=1", "--data-block-size=128", &md ], - )?; + ))?; assert!(stderr.contains("nr data blocks")); Ok(()) } diff --git a/tests/thin_metadata_pack.rs b/tests/thin_metadata_pack.rs index b45c1bc..23e573f 100644 --- a/tests/thin_metadata_pack.rs +++ b/tests/thin_metadata_pack.rs @@ -38,8 +38,12 @@ impl<'a> Program<'a> for ThinMetadataPack { "thin_metadata_pack" } - fn path() -> &'a std::ffi::OsStr { - THIN_METADATA_PACK.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + thin_metadata_pack_cmd(args) } fn usage() -> &'a str { @@ -51,7 +55,7 @@ impl<'a> Program<'a> for ThinMetadataPack { } fn bad_option_hint(option: &str) -> String { - rust_msg::bad_option_hint(option) + msg::bad_option_hint(option) } } @@ -61,21 +65,21 @@ impl<'a> InputProgram<'a> for ThinMetadataPack { } fn file_not_found() -> &'a str { - rust_msg::FILE_NOT_FOUND + msg::FILE_NOT_FOUND } fn missing_input_arg() -> &'a str { - rust_msg::MISSING_INPUT_ARG + msg::MISSING_INPUT_ARG } fn corrupted_input() -> &'a str { - rust_msg::BAD_SUPERBLOCK + msg::BAD_SUPERBLOCK } } impl<'a> OutputProgram<'a> for ThinMetadataPack { fn missing_output_arg() -> &'a str { - rust_msg::MISSING_OUTPUT_ARG + msg::MISSING_OUTPUT_ARG } } diff --git a/tests/thin_metadata_unpack.rs b/tests/thin_metadata_unpack.rs index fe77f22..bdbadb1 100644 --- a/tests/thin_metadata_unpack.rs +++ b/tests/thin_metadata_unpack.rs @@ -40,8 +40,12 @@ impl<'a> Program<'a> for ThinMetadataUnpack { "thin_metadata_pack" } - fn path() -> &'a std::ffi::OsStr { - THIN_METADATA_UNPACK.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + thin_metadata_unpack_cmd(args) } fn usage() -> &'a str { @@ -53,7 +57,7 @@ impl<'a> Program<'a> for ThinMetadataUnpack { } fn bad_option_hint(option: &str) -> String { - rust_msg::bad_option_hint(option) + msg::bad_option_hint(option) } } @@ -63,11 +67,11 @@ impl<'a> InputProgram<'a> for ThinMetadataUnpack { } fn file_not_found() -> &'a str { - rust_msg::FILE_NOT_FOUND + msg::FILE_NOT_FOUND } fn missing_input_arg() -> &'a str { - rust_msg::MISSING_INPUT_ARG + msg::MISSING_INPUT_ARG } fn corrupted_input() -> &'a str { @@ -77,7 +81,7 @@ impl<'a> InputProgram<'a> for ThinMetadataUnpack { impl<'a> OutputProgram<'a> for ThinMetadataUnpack { fn missing_output_arg() -> &'a str { - rust_msg::MISSING_OUTPUT_ARG + msg::MISSING_OUTPUT_ARG } } @@ -102,14 +106,11 @@ fn end_to_end() -> Result<()> { let mut td = TestDir::new()?; let md_in = mk_valid_md(&mut td)?; let md_out = mk_zeroed_md(&mut td)?; - run_ok(THIN_METADATA_PACK, args!["-i", &md_in, "-o", "meta.pack"])?; - run_ok( - THIN_METADATA_UNPACK, - args!["-i", "meta.pack", "-o", &md_out], - )?; + run_ok(thin_metadata_pack_cmd(args!["-i", &md_in, "-o", "meta.pack"]))?; + run_ok(thin_metadata_unpack_cmd(args!["-i", "meta.pack", "-o", &md_out]))?; - let dump1 = run_ok(THIN_DUMP, args![&md_in])?; - let dump2 = run_ok(THIN_DUMP, args![&md_out])?; + let dump1 = run_ok(thin_dump_cmd(args![&md_in]))?; + let dump2 = run_ok(thin_dump_cmd(args![&md_out]))?; assert_eq!(dump1, dump2); Ok(()) } diff --git a/tests/thin_repair.rs b/tests/thin_repair.rs index a5acf31..e99a0ea 100644 --- a/tests/thin_repair.rs +++ b/tests/thin_repair.rs @@ -33,8 +33,12 @@ impl<'a> Program<'a> for ThinRepair { "thin_repair" } - fn path() -> &'a std::ffi::OsStr { - THIN_REPAIR.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + thin_repair_cmd(args) } fn usage() -> &'a str { @@ -108,7 +112,7 @@ fn dont_repair_xml() -> Result<()> { let mut td = TestDir::new()?; let md = mk_zeroed_md(&mut td)?; let xml = mk_valid_xml(&mut td)?; - run_fail(THIN_REPAIR, args!["-i", &xml, "-o", &md])?; + run_fail(thin_repair_cmd(args!["-i", &xml, "-o", &md]))?; Ok(()) } @@ -121,9 +125,9 @@ fn override_thing(flag: &str, val: &str, pattern: &str) -> Result<()> { let mut td = TestDir::new()?; let md1 = mk_valid_md(&mut td)?; let md2 = mk_zeroed_md(&mut td)?; - let output = run_ok_raw(THIN_REPAIR, args![flag, val, "-i", &md1, "-o", &md2])?; + let output = run_ok_raw(thin_repair_cmd(args![flag, val, "-i", &md1, "-o", &md2]))?; assert_eq!(output.stderr.len(), 0); - let output = run_ok(THIN_DUMP, args![&md2])?; + let output = run_ok(thin_dump_cmd(args![&md2]))?; assert!(output.contains(pattern)); Ok(()) } @@ -151,14 +155,14 @@ fn override_nr_data_blocks() -> Result<()> { fn superblock_succeeds() -> Result<()> { let mut td = TestDir::new()?; let md1 = mk_valid_md(&mut td)?; - let original = run_ok_raw(THIN_DUMP, args![&md1])?; + let original = run_ok_raw(thin_dump_cmd(args![&md1]))?; if !cfg!(feature = "rust_tests") { assert_eq!(original.stderr.len(), 0); } damage_superblock(&md1)?; let md2 = mk_zeroed_md(&mut td)?; run_ok( - THIN_REPAIR, + thin_repair_cmd( args![ "--transaction-id=1", "--data-block-size=128", @@ -168,8 +172,8 @@ fn superblock_succeeds() -> Result<()> { "-o", &md2 ], - )?; - let repaired = run_ok_raw(THIN_DUMP, args![&md2])?; + ))?; + let repaired = run_ok_raw(thin_dump_cmd(args![&md2]))?; if !cfg!(feature = "rust_tests") { assert_eq!(repaired.stderr.len(), 0); } @@ -186,7 +190,7 @@ fn missing_thing(flag1: &str, flag2: &str, pattern: &str) -> Result<()> { let md1 = mk_valid_md(&mut td)?; damage_superblock(&md1)?; let md2 = mk_zeroed_md(&mut td)?; - let stderr = run_fail(THIN_REPAIR, args![flag1, flag2, "-i", &md1, "-o", &md2])?; + let stderr = run_fail(thin_repair_cmd(args![flag1, flag2, "-i", &md1, "-o", &md2]))?; assert!(stderr.contains(pattern)); Ok(()) } diff --git a/tests/thin_restore.rs b/tests/thin_restore.rs index da2d90a..6eae528 100644 --- a/tests/thin_restore.rs +++ b/tests/thin_restore.rs @@ -34,8 +34,12 @@ impl<'a> Program<'a> for ThinRestore { "thin_restore" } - fn path() -> &'a std::ffi::OsStr { - THIN_RESTORE.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + thin_restore_cmd(args) } fn usage() -> &'a str { @@ -104,7 +108,7 @@ fn quiet_flag(flag: &str) -> Result<()> { let xml = mk_valid_xml(&mut td)?; let md = mk_zeroed_md(&mut td)?; - let output = run_ok_raw(THIN_RESTORE, args!["-i", &xml, "-o", &md, flag])?; + let output = run_ok_raw(thin_restore_cmd(args!["-i", &xml, "-o", &md, flag]))?; assert_eq!(output.stdout.len(), 0); assert_eq!(output.stderr.len(), 0); @@ -130,9 +134,9 @@ fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> { let xml = mk_valid_xml(&mut td)?; let md = mk_zeroed_md(&mut td)?; - run_ok(THIN_RESTORE, args!["-i", &xml, "-o", &md, flag, value])?; + run_ok(thin_restore_cmd(args!["-i", &xml, "-o", &md, flag, value]))?; - let output = run_ok(THIN_DUMP, args![&md])?; + let output = run_ok(thin_dump_cmd(args![&md]))?; assert!(output.contains(pattern)); Ok(()) } diff --git a/tests/thin_rmap.rs b/tests/thin_rmap.rs index 8b58e28..699e85f 100644 --- a/tests/thin_rmap.rs +++ b/tests/thin_rmap.rs @@ -29,8 +29,12 @@ impl<'a> Program<'a> for ThinRmap { "thin_rmap" } - fn path() -> &'a std::ffi::OsStr { - THIN_RMAP.as_ref() + fn cmd(args: I) -> duct::Expression + where + I: IntoIterator, + I::Item: Into, + { + thin_rmap_cmd(args) } fn usage() -> &'a str { @@ -42,7 +46,7 @@ impl<'a> Program<'a> for ThinRmap { } fn bad_option_hint(option: &str) -> String { - cpp_msg::bad_option_hint(option) + msg::bad_option_hint(option) } } @@ -58,7 +62,7 @@ test_rejects_bad_option!(ThinRmap); fn valid_region_format_should_pass() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_ok(THIN_RMAP, args!["--region", "23..7890", &md])?; + run_ok(thin_rmap_cmd(args!["--region", "23..7890", &md]))?; Ok(()) } @@ -77,7 +81,7 @@ fn invalid_regions_should_fail() -> Result<()> { for r in &invalid_regions { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_fail(THIN_RMAP, args![r, &md])?; + run_fail(thin_rmap_cmd(args![r, &md]))?; } Ok(()) } @@ -86,10 +90,9 @@ fn invalid_regions_should_fail() -> Result<()> { fn multiple_regions_should_pass() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - run_ok( - THIN_RMAP, - args!["--region", "1..23", "--region", "45..78", &md], - )?; + run_ok(thin_rmap_cmd(args![ + "--region", "1..23", "--region", "45..78", &md + ]))?; Ok(()) } @@ -97,7 +100,7 @@ fn multiple_regions_should_pass() -> Result<()> { fn junk_input() -> Result<()> { let mut td = TestDir::new()?; let xml = mk_valid_xml(&mut td)?; - run_fail(THIN_RMAP, args!["--region", "0..-1", &xml])?; + run_fail(thin_rmap_cmd(args!["--region", "0..-1", &xml]))?; Ok(()) }