diff --git a/src/bin/cache_dump.rs b/src/bin/cache_dump.rs index 3f585bf..e706f0b 100644 --- a/src/bin/cache_dump.rs +++ b/src/bin/cache_dump.rs @@ -10,24 +10,38 @@ use thinp::cache::dump::{dump, CacheDumpOptions}; fn main() { let parser = App::new("cache_dump") .version(thinp::version::tools_version()) - .arg( - Arg::with_name("INPUT") - .help("Specify the input device to check") - .required(true) - .index(1), - ) + .about("Dump the cache metadata to stdout in XML format") .arg( Arg::with_name("REPAIR") - .help("") - .long("repair") - .value_name("REPAIR"), + .help("Repair the metadata whilst dumping it") + .short("r") + .long("repair"), + ) + .arg( + Arg::with_name("OUTPUT") + .help("Specify the output file rather than stdout") + .short("o") + .long("output") + .value_name("OUTPUT"), + ) + .arg( + Arg::with_name("INPUT") + .help("Specify the input device to dump") + .required(true) + .index(1), ); let matches = parser.get_matches(); 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())) + } else { + None + }; let opts = CacheDumpOptions { - dev: &input_file, + input: input_file, + output: output_file, async_io: false, repair: matches.is_present("REPAIR"), }; diff --git a/src/bin/thin_dump.rs b/src/bin/thin_dump.rs index 3008e35..b9f08d0 100644 --- a/src/bin/thin_dump.rs +++ b/src/bin/thin_dump.rs @@ -12,9 +12,9 @@ use thinp::report::*; use thinp::thin::dump::{dump, ThinDumpOptions}; fn main() { - let parser = App::new("thin_check") + let parser = App::new("thin_dump") .version(thinp::version::tools_version()) - .about("Validates thin provisioning metadata on a device or file.") + .about("Dump thin-provisioning metadata to stdout in XML format") .arg( Arg::with_name("QUIET") .help("Suppress output messages, return only exit code.") @@ -22,60 +22,49 @@ fn main() { .long("quiet"), ) .arg( - Arg::with_name("SB_ONLY") - .help("Only check the superblock.") - .long("super-block-only") - .value_name("SB_ONLY"), + Arg::with_name("REPAIR") + .help("Repair the metadata whilst dumping it") + .short("r") + .long("repair"), ) .arg( Arg::with_name("SKIP_MAPPINGS") - .help("Don't check the mapping tree") - .long("skip-mappings") - .value_name("SKIP_MAPPINGS"), - ) - .arg( - Arg::with_name("AUTO_REPAIR") - .help("Auto repair trivial issues.") - .long("auto-repair"), - ) - .arg( - Arg::with_name("IGNORE_NON_FATAL") - .help("Only return a non-zero exit code if a fatal error is found.") - .long("ignore-non-fatal-errors"), - ) - .arg( - Arg::with_name("CLEAR_NEEDS_CHECK") - .help("Clears the 'needs_check' flag in the superblock") - .long("clear-needs-check"), - ) - .arg( - Arg::with_name("OVERRIDE_MAPPING_ROOT") - .help("Specify a mapping root to use") - .long("override-mapping-root") - .value_name("OVERRIDE_MAPPING_ROOT") - .takes_value(true), - ) - .arg( - Arg::with_name("METADATA_SNAPSHOT") - .help("Check the metadata snapshot on a live pool") - .short("m") - .long("metadata-snapshot") - .value_name("METADATA_SNAPSHOT"), - ) - .arg( - Arg::with_name("INPUT") - .help("Specify the input device to check") - .required(true) - .index(1), + .help("Do not dump the mappings") + .long("skip-mappings"), ) .arg( Arg::with_name("SYNC_IO") .help("Force use of synchronous io") .long("sync-io"), + ) + .arg( + Arg::with_name("METADATA_SNAPSHOT") + .help("Access the metadata snapshot on a live pool") + .short("m") + .long("metadata-snapshot") + .value_name("METADATA_SNAPSHOT"), + ) + .arg( + Arg::with_name("OUTPUT") + .help("Specify the output file rather than stdout") + .short("o") + .long("output") + .value_name("OUTPUT"), + ) + .arg( + Arg::with_name("INPUT") + .help("Specify the input device to dump") + .required(true) + .index(1), ); let matches = parser.get_matches(); 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())) + } else { + None + }; if !file_utils::file_exists(input_file) { eprintln!("Couldn't find input file '{:?}'.", &input_file); @@ -93,7 +82,8 @@ fn main() { } let opts = ThinDumpOptions { - dev: &input_file, + input: input_file, + output: output_file, async_io: !matches.is_present("SYNC_IO"), report, }; diff --git a/src/cache/dump.rs b/src/cache/dump.rs index f80421a..a3685f5 100644 --- a/src/cache/dump.rs +++ b/src/cache/dump.rs @@ -1,5 +1,8 @@ use anyhow::anyhow; use fixedbitset::FixedBitSet; +use std::fs::File; +use std::io::BufWriter; +use std::io::Write; use std::path::Path; use std::sync::{Arc, Mutex}; @@ -191,7 +194,8 @@ impl<'a> ArrayVisitor for HintEmitter<'a> { //------------------------------------------ pub struct CacheDumpOptions<'a> { - pub dev: &'a Path, + pub input: &'a Path, + pub output: Option<&'a Path>, pub async_io: bool, pub repair: bool, } @@ -204,19 +208,24 @@ fn mk_context(opts: &CacheDumpOptions) -> anyhow::Result { let engine: Arc; if opts.async_io { - engine = Arc::new(AsyncIoEngine::new(opts.dev, MAX_CONCURRENT_IO, false)?); + engine = Arc::new(AsyncIoEngine::new(opts.input, MAX_CONCURRENT_IO, false)?); } else { let nr_threads = std::cmp::max(8, num_cpus::get() * 2); - engine = Arc::new(SyncIoEngine::new(opts.dev, nr_threads, false)?); + engine = Arc::new(SyncIoEngine::new(opts.input, nr_threads, false)?); } Ok(Context { engine }) } -fn dump_metadata(ctx: &Context, sb: &Superblock, _repair: bool) -> anyhow::Result<()> { +fn dump_metadata( + ctx: &Context, + w: &mut dyn Write, + sb: &Superblock, + _repair: bool, +) -> anyhow::Result<()> { let engine = &ctx.engine; - let mut out = xml::XmlWriter::new(std::io::stdout()); + let mut out = xml::XmlWriter::new(w); let xml_sb = xml::Superblock { uuid: "".to_string(), block_size: sb.data_block_size, @@ -272,6 +281,7 @@ fn dump_metadata(ctx: &Context, sb: &Superblock, _repair: bool) -> anyhow::Resul out.hints_e()?; out.superblock_e()?; + out.eof()?; Ok(()) } @@ -281,7 +291,14 @@ pub fn dump(opts: CacheDumpOptions) -> anyhow::Result<()> { let engine = &ctx.engine; let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?; - dump_metadata(&ctx, &sb, opts.repair) + let mut writer: Box; + if opts.output.is_some() { + writer = Box::new(BufWriter::new(File::create(opts.output.unwrap())?)); + } else { + writer = Box::new(BufWriter::new(std::io::stdout())); + } + + dump_metadata(&ctx, &mut writer, &sb, opts.repair) } //------------------------------------------ diff --git a/src/thin/dump.rs b/src/thin/dump.rs index d8c796f..aa5942e 100644 --- a/src/thin/dump.rs +++ b/src/thin/dump.rs @@ -1,5 +1,7 @@ use anyhow::{anyhow, Result}; use std::collections::{BTreeMap, BTreeSet}; +use std::fs::File; +use std::io::BufWriter; use std::io::Write; use std::ops::DerefMut; use std::path::Path; @@ -145,7 +147,8 @@ impl<'a> NodeVisitor for MappingVisitor<'a> { const MAX_CONCURRENT_IO: u32 = 1024; pub struct ThinDumpOptions<'a> { - pub dev: &'a Path, + pub input: &'a Path, + pub output: Option<&'a Path>, pub async_io: bool, pub report: Arc, } @@ -159,10 +162,10 @@ fn mk_context(opts: &ThinDumpOptions) -> Result { let engine: Arc; if opts.async_io { - engine = Arc::new(AsyncIoEngine::new(opts.dev, MAX_CONCURRENT_IO, false)?); + engine = Arc::new(AsyncIoEngine::new(opts.input, MAX_CONCURRENT_IO, false)?); } else { let nr_threads = std::cmp::max(8, num_cpus::get() * 2); - engine = Arc::new(SyncIoEngine::new(opts.dev, nr_threads, false)?); + engine = Arc::new(SyncIoEngine::new(opts.input, nr_threads, false)?); } Ok(Context { @@ -554,9 +557,9 @@ fn emit_entries( Ok(()) } -fn dump_metadata(ctx: &Context, sb: &Superblock, md: &Metadata) -> Result<()> { +fn dump_metadata(ctx: &Context, w: &mut dyn Write, sb: &Superblock, md: &Metadata) -> Result<()> { let data_root = unpack::(&sb.data_sm_root[0..])?; - let mut out = xml::XmlWriter::new(std::io::stdout()); + let mut out = xml::XmlWriter::new(w); let xml_sb = xml::Superblock { uuid: "".to_string(), time: sb.time as u64, @@ -590,6 +593,7 @@ fn dump_metadata(ctx: &Context, sb: &Superblock, md: &Metadata) -> Result<()> { out.device_e()?; } out.superblock_e()?; + out.eof()?; Ok(()) } @@ -601,11 +605,18 @@ pub fn dump(opts: ThinDumpOptions) -> Result<()> { let sb = read_superblock(ctx.engine.as_ref(), SUPERBLOCK_LOCATION)?; let md = build_metadata(&ctx, &sb)?; + let mut writer: Box; + if opts.output.is_some() { + writer = Box::new(BufWriter::new(File::create(opts.output.unwrap())?)); + } else { + writer = Box::new(BufWriter::new(std::io::stdout())); + } + ctx.report .set_title("Optimising metadata to improve leaf packing"); let md = optimise_metadata(md)?; - dump_metadata(&ctx, &sb, &md) + dump_metadata(&ctx, &mut writer, &sb, &md) } //------------------------------------------