From 255429f9323f68006dc0233a0668a39200342b2c Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Wed, 21 Feb 2024 15:25:53 +0800 Subject: [PATCH 09/10] [commands] Fix version string compatibility issue with LVM Make the displayed version string backward-compatible to LVM's _check_tool_version() parser, which is used to check cache_check's version while activating a cache device (issue #294). (cherry picked from commit 3cb749b91e5ed0dbd284ddd15b08998898f6d802) --- src/commands/cache_check.rs | 5 ++- src/commands/cache_dump.rs | 5 ++- src/commands/cache_generate_metadata.rs | 5 ++- src/commands/cache_metadata_size.rs | 9 ++++- src/commands/cache_repair.rs | 5 ++- src/commands/cache_restore.rs | 5 ++- src/commands/cache_writeback.rs | 5 ++- src/commands/era_check.rs | 5 ++- src/commands/era_dump.rs | 5 ++- src/commands/era_generate_metadata.rs | 5 ++- src/commands/era_invalidate.rs | 5 ++- src/commands/era_repair.rs | 5 ++- src/commands/era_restore.rs | 5 ++- src/commands/thin_check.rs | 5 ++- src/commands/thin_delta.rs | 49 +++++++++++++++++-------- src/commands/thin_dump.rs | 5 ++- src/commands/thin_explore.rs | 3 ++ src/commands/thin_generate_damage.rs | 5 ++- src/commands/thin_generate_metadata.rs | 5 ++- src/commands/thin_ls.rs | 5 ++- src/commands/thin_metadata_pack.rs | 9 ++++- src/commands/thin_metadata_size.rs | 9 ++++- src/commands/thin_metadata_unpack.rs | 5 ++- src/commands/thin_repair.rs | 5 ++- src/commands/thin_restore.rs | 5 ++- src/commands/thin_rmap.rs | 5 ++- src/commands/thin_shrink.rs | 9 ++++- src/commands/thin_stat.rs | 5 ++- src/commands/thin_trim.rs | 5 ++- src/version.rs | 32 ++++++++++++++++ tests/common/common_args.rs | 4 +- tests/thin_delta.rs | 12 ++---- 32 files changed, 194 insertions(+), 57 deletions(-) diff --git a/src/commands/cache_check.rs b/src/commands/cache_check.rs index dd86d492..3af29b8c 100644 --- a/src/commands/cache_check.rs +++ b/src/commands/cache_check.rs @@ -8,6 +8,7 @@ use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; use crate::report::*; +use crate::version::*; //------------------------------------------ @@ -18,6 +19,7 @@ impl CacheCheckCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Validates cache metadata on a device or file.") // flags .arg( @@ -76,7 +78,7 @@ impl CacheCheckCommand { .required(true) .index(1), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -87,6 +89,7 @@ impl<'a> Command<'a> for CacheCheckCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); diff --git a/src/commands/cache_dump.rs b/src/commands/cache_dump.rs index f087536d..43126495 100644 --- a/src/commands/cache_dump.rs +++ b/src/commands/cache_dump.rs @@ -7,6 +7,7 @@ use crate::cache::dump::{dump, CacheDumpOptions}; use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; +use crate::version::*; //------------------------------------------ @@ -17,6 +18,7 @@ impl CacheDumpCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Dump the cache metadata to stdout in XML format") .arg( Arg::new("REPAIR") @@ -40,7 +42,7 @@ impl CacheDumpCommand { .required(true) .index(1), ); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -51,6 +53,7 @@ impl<'a> Command<'a> for CacheDumpCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = matches.get_one::("OUTPUT").map(Path::new); diff --git a/src/commands/cache_generate_metadata.rs b/src/commands/cache_generate_metadata.rs index 449dfad4..0818e46b 100644 --- a/src/commands/cache_generate_metadata.rs +++ b/src/commands/cache_generate_metadata.rs @@ -8,6 +8,7 @@ use crate::cache::metadata_generator::*; use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; +use crate::version::*; //------------------------------------------ @@ -18,6 +19,7 @@ impl CacheGenerateMetadataCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("A tool for creating synthetic cache metadata.") // flags .arg( @@ -128,7 +130,7 @@ impl CacheGenerateMetadataCommand { ]) .required(true), ); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -139,6 +141,7 @@ impl<'a> Command<'a> for CacheGenerateMetadataCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let output_file = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/cache_metadata_size.rs b/src/commands/cache_metadata_size.rs index 8189e8cf..0083f44b 100644 --- a/src/commands/cache_metadata_size.rs +++ b/src/commands/cache_metadata_size.rs @@ -11,6 +11,7 @@ use crate::commands::Command; use crate::math::div_up; use crate::report::mk_simple_report; use crate::units::*; +use crate::version::*; //------------------------------------------ @@ -40,9 +41,10 @@ pub struct CacheMetadataSizeCommand; impl CacheMetadataSizeCommand { fn cli(&self) -> clap::Command { - clap::Command::new(self.name()) + let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Estimate the size of the metadata device needed for a given configuration.") .override_usage("cache_metadata_size [OPTIONS] <--device-size --block-size | --nr-blocks >") // flags @@ -98,7 +100,9 @@ impl CacheMetadataSizeCommand { .value_name("UNIT") .value_parser(value_parser!(Units)) .default_value("sector"), - ) + ); + + version_args(cmd) } fn parse_args(&self, args: I) -> Result<(CacheMetadataSizeOptions, Units, OutputFormat)> @@ -107,6 +111,7 @@ impl CacheMetadataSizeCommand { T: Into + Clone, { let matches = self.cli().get_matches_from(args); + display_version(&matches); let nr_blocks = matches.get_one::("NR_BLOCKS"); let device_size = matches.get_one::("DEVICE_SIZE"); diff --git a/src/commands/cache_repair.rs b/src/commands/cache_repair.rs index 2098548e..658e096c 100644 --- a/src/commands/cache_repair.rs +++ b/src/commands/cache_repair.rs @@ -8,6 +8,7 @@ use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; use crate::report::*; +use crate::version::*; pub struct CacheRepairCommand; @@ -16,6 +17,7 @@ impl CacheRepairCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Repair binary cache metadata, and write it to a different device or file") .arg( Arg::new("QUIET") @@ -44,7 +46,7 @@ impl CacheRepairCommand { // a dummy argument for compatibility with lvconvert .arg(Arg::new("DUMMY").required(false).hide(true).index(1)); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -55,6 +57,7 @@ impl<'a> Command<'a> for CacheRepairCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let report = mk_report(matches.get_flag("QUIET")); let log_level = match parse_log_level(&matches) { diff --git a/src/commands/cache_restore.rs b/src/commands/cache_restore.rs index 609d2a10..b88d9c92 100644 --- a/src/commands/cache_restore.rs +++ b/src/commands/cache_restore.rs @@ -9,6 +9,7 @@ use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; use crate::report::{parse_log_level, verbose_args}; +use crate::version::*; pub struct CacheRestoreCommand; @@ -17,6 +18,7 @@ impl CacheRestoreCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Convert XML format metadata to binary.") .arg( Arg::new("QUIET") @@ -58,7 +60,7 @@ impl CacheRestoreCommand { .value_name("FILE") .required(true), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -69,6 +71,7 @@ impl<'a> Command<'a> for CacheRestoreCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/cache_writeback.rs b/src/commands/cache_writeback.rs index 3e7be9a2..875188a3 100644 --- a/src/commands/cache_writeback.rs +++ b/src/commands/cache_writeback.rs @@ -7,6 +7,7 @@ use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; use crate::report::{parse_log_level, verbose_args}; +use crate::version::*; pub struct CacheWritebackCommand; @@ -15,6 +16,7 @@ impl CacheWritebackCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Repair binary cache metadata, and write it to a different device or file") .arg( Arg::new("QUIET") @@ -88,7 +90,7 @@ impl CacheWritebackCommand { .value_parser(value_parser!(u32)) .default_value("0"), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -99,6 +101,7 @@ impl<'a> Command<'a> for CacheWritebackCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let metadata_dev = Path::new(matches.get_one::("METADATA_DEV").unwrap()); let origin_dev = Path::new(matches.get_one::("ORIGIN_DEV").unwrap()); diff --git a/src/commands/era_check.rs b/src/commands/era_check.rs index 7ee03ded..fefd772b 100644 --- a/src/commands/era_check.rs +++ b/src/commands/era_check.rs @@ -8,6 +8,7 @@ use crate::commands::utils::*; use crate::commands::Command; use crate::era::check::{check, EraCheckOptions}; use crate::report::*; +use crate::version::*; //------------------------------------------ @@ -18,6 +19,7 @@ impl EraCheckCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Validate era metadata on device or file.") // flags .arg( @@ -46,7 +48,7 @@ impl EraCheckCommand { .required(true) .index(1), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -57,6 +59,7 @@ impl<'a> Command<'a> for EraCheckCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); diff --git a/src/commands/era_dump.rs b/src/commands/era_dump.rs index 35100b05..e0f9919e 100644 --- a/src/commands/era_dump.rs +++ b/src/commands/era_dump.rs @@ -7,6 +7,7 @@ use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; use crate::era::dump::{dump, EraDumpOptions}; +use crate::version::*; //------------------------------------------ @@ -17,6 +18,7 @@ impl EraDumpCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Dump the era metadata to stdout in XML format") .arg( Arg::new("LOGICAL") @@ -46,7 +48,7 @@ impl EraDumpCommand { .required(true) .index(1), ); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -57,6 +59,7 @@ impl<'a> Command<'a> for EraDumpCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = matches.get_one::("OUTPUT").map(Path::new); diff --git a/src/commands/era_generate_metadata.rs b/src/commands/era_generate_metadata.rs index 58c46f25..9028c5da 100644 --- a/src/commands/era_generate_metadata.rs +++ b/src/commands/era_generate_metadata.rs @@ -7,6 +7,7 @@ use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; use crate::era::metadata_generator::*; +use crate::version::*; //------------------------------------------ @@ -17,6 +18,7 @@ impl EraGenerateMetadataCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("A tool for creating synthetic era metadata.") // flags .arg( @@ -67,7 +69,7 @@ impl EraGenerateMetadataCommand { .required(true), ) .group(ArgGroup::new("commands").args(["FORMAT"]).required(true)); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -78,6 +80,7 @@ impl<'a> Command<'a> for EraGenerateMetadataCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let output_file = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/era_invalidate.rs b/src/commands/era_invalidate.rs index cf5c14f9..58032112 100644 --- a/src/commands/era_invalidate.rs +++ b/src/commands/era_invalidate.rs @@ -5,6 +5,7 @@ use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; use crate::era::invalidate::{invalidate, EraInvalidateOptions}; +use crate::version::*; //------------------------------------------ @@ -15,6 +16,7 @@ impl EraInvalidateCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("List blocks that may have changed since a given era") .arg( Arg::new("METADATA_SNAPSHOT") @@ -44,7 +46,7 @@ impl EraInvalidateCommand { .required(true) .index(1), ); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -55,6 +57,7 @@ impl<'a> Command<'a> for EraInvalidateCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = matches.get_one::("OUTPUT").map(Path::new); diff --git a/src/commands/era_repair.rs b/src/commands/era_repair.rs index e4f095c8..825c37d8 100644 --- a/src/commands/era_repair.rs +++ b/src/commands/era_repair.rs @@ -8,6 +8,7 @@ use crate::commands::utils::*; use crate::commands::Command; use crate::era::repair::{repair, EraRepairOptions}; use crate::report::*; +use crate::version::*; pub struct EraRepairCommand; @@ -16,6 +17,7 @@ impl EraRepairCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Repair binary era metadata, and write it to a different device or file") .arg( Arg::new("QUIET") @@ -41,7 +43,7 @@ impl EraRepairCommand { .value_name("FILE") .required(true), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -52,6 +54,7 @@ impl<'a> Command<'a> for EraRepairCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/era_restore.rs b/src/commands/era_restore.rs index 9c1184be..b779ac98 100644 --- a/src/commands/era_restore.rs +++ b/src/commands/era_restore.rs @@ -8,6 +8,7 @@ use crate::commands::utils::*; use crate::commands::Command; use crate::era::restore::{restore, EraRestoreOptions}; use crate::report::{parse_log_level, verbose_args}; +use crate::version::*; pub struct EraRestoreCommand; @@ -16,6 +17,7 @@ impl EraRestoreCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Convert XML format metadata to binary.") // flags .arg( @@ -42,7 +44,7 @@ impl EraRestoreCommand { .value_name("FILE") .required(true), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -53,6 +55,7 @@ impl<'a> Command<'a> for EraRestoreCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/thin_check.rs b/src/commands/thin_check.rs index c505c821..f8eeafc4 100644 --- a/src/commands/thin_check.rs +++ b/src/commands/thin_check.rs @@ -6,6 +6,7 @@ use crate::commands::utils::*; use crate::commands::Command; use crate::report::{parse_log_level, verbose_args}; use crate::thin::check::{check, ThinCheckOptions}; +use crate::version::*; pub struct ThinCheckCommand; @@ -14,6 +15,7 @@ impl ThinCheckCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Validates thin provisioning metadata on a device or file.") // flags .arg( @@ -85,7 +87,7 @@ impl ThinCheckCommand { .required(true) .index(1), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -96,6 +98,7 @@ impl<'a> Command<'a> for ThinCheckCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); diff --git a/src/commands/thin_delta.rs b/src/commands/thin_delta.rs index 0d4371ac..9d50d641 100644 --- a/src/commands/thin_delta.rs +++ b/src/commands/thin_delta.rs @@ -9,6 +9,7 @@ use crate::commands::utils::*; use crate::commands::Command; use crate::thin::delta::*; use crate::thin::delta_visitor::Snap; +use crate::version::*; //------------------------------------------ @@ -19,6 +20,7 @@ impl ThinDeltaCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Print the differences in the mappings between two thin devices") .arg( Arg::new("METADATA_SNAPSHOT") @@ -72,17 +74,15 @@ impl ThinDeltaCommand { .index(1), ) // groups - .group( - ArgGroup::new("SNAP1") - .args(["ROOT1", "THIN1"]) - .required(true), - ) - .group( - ArgGroup::new("SNAP2") - .args(["ROOT2", "THIN2"]) - .required(true), - ); - engine_args(cmd) + // + // Due to a bug in clap 4.4 that doesn't handle exclusive Args + // and ArgGroup::required(true) properly, we avoid using the + // 'required(true)' flag on ArgGroup, and perform manual checks + // instead. (see github clap-rs/clap#5041) + .group(ArgGroup::new("SNAP1").args(["ROOT1", "THIN1"])) + .group(ArgGroup::new("SNAP2").args(["ROOT2", "THIN2"])); + + engine_args(version_args(cmd)) } } @@ -93,6 +93,7 @@ impl<'a> Command<'a> for ThinDeltaCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); @@ -102,16 +103,34 @@ impl<'a> Command<'a> for ThinDeltaCommand { return to_exit_code::<()>(&report, Err(e)); } - let snap1 = match matches.get_one::("SNAP1").unwrap().as_str() { + let snap1 = match matches + .get_one::("SNAP1") + .unwrap_or(&clap::Id::default()) + .as_str() + { "THIN1" => Snap::DeviceId(*matches.get_one::("THIN1").unwrap()), "ROOT1" => Snap::RootBlock(*matches.get_one::("ROOT1").unwrap()), - _ => return to_exit_code::<()>(&report, Err(anyhow!("unknown option"))), + _ => { + return to_exit_code::<()>( + &report, + Err(anyhow!("--thin1 or --root1 not specified")), + ) + } }; - let snap2 = match matches.get_one::("SNAP2").unwrap().as_str() { + let snap2 = match matches + .get_one::("SNAP2") + .unwrap_or(&clap::Id::default()) + .as_str() + { "THIN2" => Snap::DeviceId(*matches.get_one::("THIN2").unwrap()), "ROOT2" => Snap::RootBlock(*matches.get_one::("ROOT2").unwrap()), - _ => return to_exit_code::<()>(&report, Err(anyhow!("unknown option"))), + _ => { + return to_exit_code::<()>( + &report, + Err(anyhow!("--thin2 or --root2 not specified")), + ) + } }; let engine_opts = parse_engine_opts(ToolType::Thin, &matches); diff --git a/src/commands/thin_dump.rs b/src/commands/thin_dump.rs index 96cd8e87..d876066f 100644 --- a/src/commands/thin_dump.rs +++ b/src/commands/thin_dump.rs @@ -10,6 +10,7 @@ use crate::commands::Command; use crate::report::*; use crate::thin::dump::{dump, OutputFormat, ThinDumpOptions}; use crate::thin::metadata_repair::SuperblockOverrides; +use crate::version::*; pub struct ThinDumpCommand; @@ -18,6 +19,7 @@ impl ThinDumpCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Dump thin-provisioning metadata to stdout in XML format") .arg( Arg::new("QUIET") @@ -108,7 +110,7 @@ impl ThinDumpCommand { .required(true) .index(1), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -119,6 +121,7 @@ impl<'a> Command<'a> for ThinDumpCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = matches.get_one::("OUTPUT").map(Path::new); diff --git a/src/commands/thin_explore.rs b/src/commands/thin_explore.rs index 79ca6ff2..5b6c7fac 100644 --- a/src/commands/thin_explore.rs +++ b/src/commands/thin_explore.rs @@ -39,6 +39,7 @@ use crate::pdata::unpack::*; use crate::thin::block_time::*; use crate::thin::device_detail::*; use crate::thin::superblock::*; +use crate::version::*; //------------------------------------ @@ -879,6 +880,7 @@ impl ThinExploreCommand { clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("A text user interface for examining thin metadata.") .arg( Arg::new("NODE_PATH") @@ -903,6 +905,7 @@ impl<'a> Command<'a> for ThinExploreCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let node_path = matches .get_one::("NODE_PATH") diff --git a/src/commands/thin_generate_damage.rs b/src/commands/thin_generate_damage.rs index f6d9f47c..080a0325 100644 --- a/src/commands/thin_generate_damage.rs +++ b/src/commands/thin_generate_damage.rs @@ -5,6 +5,7 @@ use std::process; use crate::commands::engine::*; use crate::commands::utils::*; use crate::thin::damage_generator::*; +use crate::version::*; //------------------------------------------ use crate::commands::Command; @@ -16,6 +17,7 @@ impl ThinGenerateDamageCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("A tool for creating synthetic thin metadata.") .arg( Arg::new("CREATE_METADATA_LEAKS") @@ -92,7 +94,7 @@ impl ThinGenerateDamageCommand { .args(["MAPPING_ROOT", "DETAILS_ROOT", "METADATA_SNAPSHOT"]) .multiple(true), ); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -103,6 +105,7 @@ impl<'a> Command<'a> for ThinGenerateDamageCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let report = mk_report(false); diff --git a/src/commands/thin_generate_metadata.rs b/src/commands/thin_generate_metadata.rs index 14a59a01..7e445d28 100644 --- a/src/commands/thin_generate_metadata.rs +++ b/src/commands/thin_generate_metadata.rs @@ -5,6 +5,7 @@ use std::process; use crate::commands::engine::*; use crate::commands::utils::*; use crate::thin::metadata_generator::*; +use crate::version::*; //------------------------------------------ use crate::commands::Command; @@ -16,6 +17,7 @@ impl ThinGenerateMetadataCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("A tool for creating synthetic thin metadata.") // flags .arg( @@ -64,7 +66,7 @@ impl ThinGenerateMetadataCommand { .args(["FORMAT", "SET_NEEDS_CHECK"]) .required(true), ); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -75,6 +77,7 @@ impl<'a> Command<'a> for ThinGenerateMetadataCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let report = mk_report(false); diff --git a/src/commands/thin_ls.rs b/src/commands/thin_ls.rs index 7d778d31..20dcd582 100644 --- a/src/commands/thin_ls.rs +++ b/src/commands/thin_ls.rs @@ -8,6 +8,7 @@ use crate::commands::utils::*; use crate::commands::Command; use crate::report::{parse_log_level, verbose_args}; use crate::thin::ls::*; +use crate::version::*; pub struct ThinLsCommand; @@ -16,6 +17,7 @@ impl ThinLsCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("List thin volumes within a pool") .arg( Arg::new("NO_HEADERS") @@ -47,7 +49,7 @@ impl ThinLsCommand { .required(true) .index(1), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -60,6 +62,7 @@ impl<'a> Command<'a> for ThinLsCommand { use OutputField::*; let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); diff --git a/src/commands/thin_metadata_pack.rs b/src/commands/thin_metadata_pack.rs index 265439c9..be0c7585 100644 --- a/src/commands/thin_metadata_pack.rs +++ b/src/commands/thin_metadata_pack.rs @@ -6,14 +6,16 @@ use std::path::Path; use crate::commands::utils::*; use crate::commands::Command; use crate::report::*; +use crate::version::*; pub struct ThinMetadataPackCommand; impl ThinMetadataPackCommand { fn cli(&self) -> clap::Command { - clap::Command::new(self.name()) + let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Produces a compressed file of thin metadata. Only packs metadata blocks that are actually used.") // flags .arg(Arg::new("FORCE") @@ -33,7 +35,9 @@ impl ThinMetadataPackCommand { .required(true) .short('o') .long("output") - .value_name("FILE")) + .value_name("FILE")); + + version_args(cmd) } } @@ -44,6 +48,7 @@ impl<'a> Command<'a> for ThinMetadataPackCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/thin_metadata_size.rs b/src/commands/thin_metadata_size.rs index 86f04b3a..b1c18678 100644 --- a/src/commands/thin_metadata_size.rs +++ b/src/commands/thin_metadata_size.rs @@ -10,6 +10,7 @@ use crate::commands::Command; use crate::report::mk_simple_report; use crate::thin::metadata_size::*; use crate::units::*; +use crate::version::*; //------------------------------------------ @@ -39,9 +40,10 @@ pub struct ThinMetadataSizeCommand; impl ThinMetadataSizeCommand { fn cli(&self) -> clap::Command { - clap::Command::new(self.name()) + let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Estimate the size of the metadata device needed for a given configuration.") // flags .arg( @@ -94,7 +96,9 @@ impl ThinMetadataSizeCommand { .value_name("UNIT") .value_parser(value_parser!(Units)) .default_value("sector"), - ) + ); + + version_args(cmd) } fn parse_args(&self, args: I) -> Result<(ThinMetadataSizeOptions, Units, OutputFormat)> @@ -103,6 +107,7 @@ impl ThinMetadataSizeCommand { T: Into + Clone, { let matches = self.cli().get_matches_from(args); + display_version(&matches); let pool_size = matches .get_one::("POOL_SIZE") diff --git a/src/commands/thin_metadata_unpack.rs b/src/commands/thin_metadata_unpack.rs index 7e383ce3..6fbb8437 100644 --- a/src/commands/thin_metadata_unpack.rs +++ b/src/commands/thin_metadata_unpack.rs @@ -8,6 +8,7 @@ use crate::commands::utils::*; use crate::commands::Command; use crate::pack::toplevel::unpack; use crate::report::mk_simple_report; +use crate::version::*; pub struct ThinMetadataUnpackCommand; @@ -16,6 +17,7 @@ impl ThinMetadataUnpackCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Unpack a compressed file of thin metadata.") // flags .arg( @@ -40,7 +42,7 @@ impl ThinMetadataUnpackCommand { .short('o') .value_name("DEV"), ); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -51,6 +53,7 @@ impl<'a> Command<'a> for ThinMetadataUnpackCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/thin_repair.rs b/src/commands/thin_repair.rs index 6f4384cc..5abc5e36 100644 --- a/src/commands/thin_repair.rs +++ b/src/commands/thin_repair.rs @@ -9,6 +9,7 @@ use crate::commands::Command; use crate::report::{parse_log_level, verbose_args}; use crate::thin::metadata_repair::SuperblockOverrides; use crate::thin::repair::{repair, ThinRepairOptions}; +use crate::version::*; pub struct ThinRepairCommand; @@ -17,6 +18,7 @@ impl ThinRepairCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Repair thin-provisioning metadata, and write it to different device or file") .arg( Arg::new("QUIET") @@ -66,7 +68,7 @@ impl ThinRepairCommand { // a dummy argument for compatibility with lvconvert .arg(Arg::new("DUMMY").required(false).hide(true).index(1)); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -77,6 +79,7 @@ impl<'a> Command<'a> for ThinRepairCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/thin_restore.rs b/src/commands/thin_restore.rs index feab577a..43becfcb 100644 --- a/src/commands/thin_restore.rs +++ b/src/commands/thin_restore.rs @@ -9,6 +9,7 @@ use crate::commands::Command; use crate::report::{parse_log_level, verbose_args}; use crate::thin::metadata_repair::SuperblockOverrides; use crate::thin::restore::{restore, ThinRestoreOptions}; +use crate::version::*; pub struct ThinRestoreCommand; @@ -17,6 +18,7 @@ impl ThinRestoreCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Convert XML format metadata to binary.") .arg( Arg::new("QUIET") @@ -63,7 +65,7 @@ impl ThinRestoreCommand { .value_name("NUM") .value_parser(value_parser!(u64)), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -74,6 +76,7 @@ impl<'a> Command<'a> for ThinRestoreCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let output_file = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/thin_rmap.rs b/src/commands/thin_rmap.rs index fef2e36a..109a8a0f 100644 --- a/src/commands/thin_rmap.rs +++ b/src/commands/thin_rmap.rs @@ -8,6 +8,7 @@ use crate::commands::engine::*; use crate::commands::utils::*; use crate::commands::Command; use crate::thin::rmap::*; +use crate::version::*; //------------------------------------------ @@ -18,6 +19,7 @@ impl ThinRmapCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Output reverse map of a thin provisioned region of blocks") // options .arg( @@ -39,7 +41,7 @@ impl ThinRmapCommand { .required(true) .index(1), ); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -50,6 +52,7 @@ impl<'a> Command<'a> for ThinRmapCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input_file = Path::new(matches.get_one::("INPUT").unwrap()); let report = mk_report(false); diff --git a/src/commands/thin_shrink.rs b/src/commands/thin_shrink.rs index 38783d41..772024cd 100644 --- a/src/commands/thin_shrink.rs +++ b/src/commands/thin_shrink.rs @@ -13,14 +13,16 @@ use crate::commands::utils::*; use crate::commands::Command; use crate::report::*; use crate::thin::shrink::{shrink, ThinShrinkOptions}; +use crate::version::*; pub struct ThinShrinkCommand; impl ThinShrinkCommand { fn cli(&self) -> clap::Command { - clap::Command::new(self.name()) + let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Rewrite xml metadata and move data in an inactive pool.") .arg( Arg::new("INPUT") @@ -64,7 +66,9 @@ impl ThinShrinkCommand { .help("Perform binary metadata rebuild rather than XML rewrite") .long("binary") .action(ArgAction::SetTrue), - ) + ); + + version_args(cmd) } fn parse_args(&self, args: I) -> io::Result @@ -73,6 +77,7 @@ impl ThinShrinkCommand { T: Into + Clone, { let matches = self.cli().get_matches_from(args); + display_version(&matches); let input = Path::new(matches.get_one::("INPUT").unwrap()); let output = Path::new(matches.get_one::("OUTPUT").unwrap()); diff --git a/src/commands/thin_stat.rs b/src/commands/thin_stat.rs index e62e7e8f..62ecb9c6 100644 --- a/src/commands/thin_stat.rs +++ b/src/commands/thin_stat.rs @@ -5,6 +5,7 @@ use crate::commands::engine::*; use crate::commands::utils::*; use crate::report::mk_simple_report; use crate::thin::stat::*; +use crate::version::*; //------------------------------------------ use crate::commands::Command; @@ -16,6 +17,7 @@ impl ThinStatCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Tool to show metadata statistics") .arg( Arg::new("OP") @@ -30,7 +32,7 @@ impl ThinStatCommand { .index(1), ); - engine_args(cmd) + engine_args(version_args(cmd)) } } @@ -41,6 +43,7 @@ impl<'a> Command<'a> for ThinStatCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let report = std::sync::Arc::new(mk_simple_report()); let engine_opts = parse_engine_opts(ToolType::Thin, &matches); diff --git a/src/commands/thin_trim.rs b/src/commands/thin_trim.rs index 9e742e15..79df88dc 100644 --- a/src/commands/thin_trim.rs +++ b/src/commands/thin_trim.rs @@ -8,6 +8,7 @@ use crate::commands::utils::*; use crate::report::{parse_log_level, verbose_args}; use crate::thin::check::{check, ThinCheckOptions}; use crate::thin::trim::{trim, ThinTrimOptions}; +use crate::version::*; //------------------------------------------ use crate::commands::Command; @@ -19,6 +20,7 @@ impl ThinTrimCommand { let cmd = clap::Command::new(self.name()) .next_display_order(None) .version(crate::tools_version!()) + .disable_version_flag(true) .about("Issue discard requests for free pool space (offline tool).") .arg( Arg::new("QUIET") @@ -42,7 +44,7 @@ impl ThinTrimCommand { .value_name("FILE") .required(true), ); - verbose_args(engine_args(cmd)) + verbose_args(engine_args(version_args(cmd))) } } @@ -53,6 +55,7 @@ impl<'a> Command<'a> for ThinTrimCommand { fn run(&self, args: &mut dyn Iterator) -> exitcode::ExitCode { let matches = self.cli().get_matches_from(args); + display_version(&matches); let metadata_dev = Path::new(matches.get_one::("METADATA_DEV").unwrap()); let data_dev = Path::new(matches.get_one::("DATA_DEV").unwrap()); diff --git a/src/version.rs b/src/version.rs index af094ef4..bf4e8dda 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,6 +1,38 @@ +use clap::ArgMatches; +use std::io::Write; + +//------------------------------------------ + #[macro_export] macro_rules! tools_version { () => { env!("CARGO_PKG_VERSION") }; } + +pub fn version_args(cmd: clap::Command) -> clap::Command { + use clap::Arg; + + cmd.arg( + Arg::new("VERSION") + .help("Print version") + .short('V') + .long("version") + .exclusive(true) + .action(clap::ArgAction::SetTrue), + ) +} + +pub fn display_version(matches: &ArgMatches) { + if matches.get_flag("VERSION") { + let mut stdout = std::io::stdout(); + // ignore broken pipe errors + let _ = stdout.write_all(tools_version!().as_bytes()); + let _ = stdout.write_all(b"\n"); + let _ = stdout.flush(); + + std::process::exit(0); + } +} + +//------------------------------------------ diff --git a/tests/common/common_args.rs b/tests/common/common_args.rs index b2da8ead..c5286749 100644 --- a/tests/common/common_args.rs +++ b/tests/common/common_args.rs @@ -50,7 +50,7 @@ where P: Program<'a>, { let stdout = run_ok(P::cmd(args!["-V"]))?; - assert!(stdout.contains(tools_version!())); + assert!(stdout.starts_with(tools_version!())); Ok(()) } @@ -59,7 +59,7 @@ where P: Program<'a>, { let stdout = run_ok(P::cmd(args!["--version"]))?; - assert!(stdout.contains(tools_version!())); + assert!(stdout.starts_with(tools_version!())); Ok(()) } diff --git a/tests/thin_delta.rs b/tests/thin_delta.rs index 0732038a..13556bf4 100644 --- a/tests/thin_delta.rs +++ b/tests/thin_delta.rs @@ -13,7 +13,7 @@ use common::thin::*; const USAGE: &str = "Print the differences in the mappings between two thin devices -Usage: thin_delta [OPTIONS] <--root1 |--thin1 > <--root2 |--thin2 > +Usage: thin_delta [OPTIONS] Arguments: Specify the input device @@ -71,10 +71,7 @@ fn snap1_unspecified() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; let stderr = run_fail(thin_delta_cmd(args!["--snap2", "45", &md]))?; - assert!(stderr.contains( - "the following required arguments were not provided: - <--root1 |--thin1 >" - )); + assert!(stderr.contains("--thin1 or --root1 not specified")); Ok(()) } @@ -83,10 +80,7 @@ fn snap2_unspecified() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; let stderr = run_fail(thin_delta_cmd(args!["--snap1", "45", &md]))?; - assert!(stderr.contains( - "the following required arguments were not provided: - <--root2 |--thin2 >" - )); + assert!(stderr.contains("--thin2 or --root2 not specified")); Ok(()) } -- 2.43.0