From f4df9bc2beca06d8a73eeaf89c2651e4491e642d Mon Sep 17 00:00:00 2001 From: mulhern Date: Fri, 5 Jan 2024 21:07:47 -0500 Subject: [PATCH 1/2] Put dump-metadata code in separate file Signed-off-by: mulhern --- src/bin/stratis-dumpmetadata.rs | 148 ++------------------------------ src/bin/tools/dump_metadata.rs | 145 +++++++++++++++++++++++++++++++ src/bin/tools/mod.rs | 5 ++ 3 files changed, 155 insertions(+), 143 deletions(-) create mode 100644 src/bin/tools/dump_metadata.rs create mode 100644 src/bin/tools/mod.rs diff --git a/src/bin/stratis-dumpmetadata.rs b/src/bin/stratis-dumpmetadata.rs index ea146bad46..cd855fb8ae 100644 --- a/src/bin/stratis-dumpmetadata.rs +++ b/src/bin/stratis-dumpmetadata.rs @@ -2,115 +2,15 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use std::{ - env, - fs::OpenOptions, - io::{Seek, SeekFrom}, - process, -}; +mod tools; + +use std::{env, process}; use env_logger::Builder; use clap::{Arg, ArgAction, Command}; -use pretty_hex::pretty_hex; - -use serde_json::Value; - -use stratisd::engine::{StaticHeader, StaticHeaderResult, BDA}; - -/// Format metadata on a given device -/// Returns StaticHeader fields -/// Returns an additional bytes buffer if print_bytes flag is True -fn fmt_metadata(shr: &StaticHeaderResult, print_bytes: bool) -> String { - let mut result = String::from("\nHeader:\n") - + shr - .header - .as_ref() - .map_or(String::from("Unreadable\n"), |h| { - h.as_ref().map_or_else( - |e| format!("Error: {}\n", e), - |s| { - s.as_ref() - .map_or(String::from("No signature buffer\n"), |sh| { - format!("{:#?}\n", sh) - }) - }, - ) - }) - .as_str(); - if print_bytes { - result += "\n\nBytes:\n\n"; - match &shr.bytes { - Ok(ref boxed) => { - result += pretty_hex(boxed.as_ref()).as_str(); - } - Err(e) => { - result += e.to_string().as_str(); - } - } - } - - result -} - -/// Prints signature block -/// Prints the sigblock bytes if print_bytes is true. -/// Skips if only_pool is true. -fn print_signature_block( - read_results: &(StaticHeaderResult, StaticHeaderResult), - print_bytes: bool, - only_pool: bool, -) { - if only_pool { - return; - } - - if read_results.0 == read_results.1 { - println!( - "Signature block: \n{}", - fmt_metadata(&read_results.0, print_bytes) - ); - } else { - println!( - "Signature block 1: \n{}", - fmt_metadata(&read_results.0, print_bytes) - ); - println!( - "Signature block 2: \n{}", - fmt_metadata(&read_results.1, print_bytes) - ); - }; -} - -/// Prints the bda. -/// Skips if only_pool is true. -fn print_bda(bda: &BDA, only_pool: bool) { - if only_pool { - return; - } - - println!("\n{:#?}", bda); -} - -/// Prints the pool level metadata. -/// Prints in machine-readable form if only_pool is true. -fn print_pool_metadata(pool_metadata: &Option>, only_pool: bool) -> Result<(), String> { - if !only_pool { - println!("\nPool metadata:"); - } - if let Some(loaded_state) = pool_metadata { - let state_json: Value = serde_json::from_slice(loaded_state) - .map_err(|extract_err| format!("Error during state JSON extract: {}", extract_err))?; - let state_json_pretty: String = serde_json::to_string_pretty(&state_json) - .map_err(|parse_err| format!("Error during state JSON parse: {}", parse_err))?; - println!("{}", state_json_pretty); - } else if !only_pool { - println!("None found"); - } - - Ok(()) -} +use tools::dump_metadata; /// Configure and initialize the logger. /// Read log configuration parameters from the environment if RUST_LOG @@ -126,44 +26,6 @@ fn initialize_log() { builder.init() } -// Print metadata, such as StaticHeaders, BDA, and Pool Metadata of given device. -// If sigblocks match, display the StaticHeader fields of a single sigblock, -// Otherwise display the StaticHeader fields of both sigblocks. -// If print_bytes flag is set to True, display the bytes buffer -// of the sigblock alongside the StaticHeader. -fn run(devpath: &str, print_bytes: bool, pool_only: bool) -> Result<(), String> { - let mut devfile = OpenOptions::new() - .read(true) - .open(devpath) - .map_err(|the_io_error| format!("Error opening device: {}", the_io_error))?; - - let read_results = StaticHeader::read_sigblocks(&mut devfile); - print_signature_block(&read_results, print_bytes, pool_only); - - let header = - StaticHeader::repair_sigblocks(&mut devfile, read_results, StaticHeader::do_nothing) - .map_err(|repair_error| format!("No valid StaticHeader found: {}", repair_error))? - .ok_or_else(|| "No valid Stratis signature found".to_string())?; - - let bda = BDA::load(header, &mut devfile) - .map_err(|bda_load_error| format!("BDA detected but error found: {}", bda_load_error))? - .ok_or_else(|| "No Stratis BDA metadata found".to_string())?; - - print_bda(&bda, pool_only); - - devfile - .seek(SeekFrom::Start(0)) - .map_err(|seek_err| format!("Error during seek: {}", seek_err))?; - - let loaded_state = bda - .load_state(&mut devfile) - .map_err(|stateload_err| format!("Error during load state: {}", stateload_err))?; - - print_pool_metadata(&loaded_state, pool_only)?; - - Ok(()) -} - fn parse_args() -> Command { Command::new("stratis-dumpmetadata") .next_line_help(true) @@ -199,7 +61,7 @@ fn main() { initialize_log(); - match run( + match dump_metadata::run( devpath, matches.get_flag("print_bytes"), matches diff --git a/src/bin/tools/dump_metadata.rs b/src/bin/tools/dump_metadata.rs new file mode 100644 index 0000000000..7fc2dfe70f --- /dev/null +++ b/src/bin/tools/dump_metadata.rs @@ -0,0 +1,145 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::{ + fs::OpenOptions, + io::{Seek, SeekFrom}, +}; + +use pretty_hex::pretty_hex; + +use serde_json::Value; + +use stratisd::engine::{StaticHeader, StaticHeaderResult, BDA}; + +/// Format metadata on a given device +/// Returns StaticHeader fields +/// Returns an additional bytes buffer if print_bytes flag is True +fn fmt_metadata(shr: &StaticHeaderResult, print_bytes: bool) -> String { + let mut result = String::from("\nHeader:\n") + + shr + .header + .as_ref() + .map_or(String::from("Unreadable\n"), |h| { + h.as_ref().map_or_else( + |e| format!("Error: {}\n", e), + |s| { + s.as_ref() + .map_or(String::from("No signature buffer\n"), |sh| { + format!("{:#?}\n", sh) + }) + }, + ) + }) + .as_str(); + if print_bytes { + result += "\n\nBytes:\n\n"; + match &shr.bytes { + Ok(ref boxed) => { + result += pretty_hex(boxed.as_ref()).as_str(); + } + Err(e) => { + result += e.to_string().as_str(); + } + } + } + + result +} + +/// Prints signature block +/// Prints the sigblock bytes if print_bytes is true. +/// Skips if only_pool is true. +fn print_signature_block( + read_results: &(StaticHeaderResult, StaticHeaderResult), + print_bytes: bool, + only_pool: bool, +) { + if only_pool { + return; + } + + if read_results.0 == read_results.1 { + println!( + "Signature block: \n{}", + fmt_metadata(&read_results.0, print_bytes) + ); + } else { + println!( + "Signature block 1: \n{}", + fmt_metadata(&read_results.0, print_bytes) + ); + println!( + "Signature block 2: \n{}", + fmt_metadata(&read_results.1, print_bytes) + ); + }; +} + +/// Prints the bda. +/// Skips if only_pool is true. +fn print_bda(bda: &BDA, only_pool: bool) { + if only_pool { + return; + } + + println!("\n{:#?}", bda); +} + +/// Prints the pool level metadata. +/// Prints in machine-readable form if only_pool is true. +fn print_pool_metadata(pool_metadata: &Option>, only_pool: bool) -> Result<(), String> { + if !only_pool { + println!("\nPool metadata:"); + } + if let Some(loaded_state) = pool_metadata { + let state_json: Value = serde_json::from_slice(loaded_state) + .map_err(|extract_err| format!("Error during state JSON extract: {}", extract_err))?; + let state_json_pretty: String = serde_json::to_string_pretty(&state_json) + .map_err(|parse_err| format!("Error during state JSON parse: {}", parse_err))?; + println!("{}", state_json_pretty); + } else if !only_pool { + println!("None found"); + } + + Ok(()) +} + +// Print metadata, such as StaticHeaders, BDA, and Pool Metadata of given device. +// If sigblocks match, display the StaticHeader fields of a single sigblock, +// Otherwise display the StaticHeader fields of both sigblocks. +// If print_bytes flag is set to True, display the bytes buffer +// of the sigblock alongside the StaticHeader. +pub fn run(devpath: &str, print_bytes: bool, pool_only: bool) -> Result<(), String> { + let mut devfile = OpenOptions::new() + .read(true) + .open(devpath) + .map_err(|the_io_error| format!("Error opening device: {}", the_io_error))?; + + let read_results = StaticHeader::read_sigblocks(&mut devfile); + print_signature_block(&read_results, print_bytes, pool_only); + + let header = + StaticHeader::repair_sigblocks(&mut devfile, read_results, StaticHeader::do_nothing) + .map_err(|repair_error| format!("No valid StaticHeader found: {}", repair_error))? + .ok_or_else(|| "No valid Stratis signature found".to_string())?; + + let bda = BDA::load(header, &mut devfile) + .map_err(|bda_load_error| format!("BDA detected but error found: {}", bda_load_error))? + .ok_or_else(|| "No Stratis BDA metadata found".to_string())?; + + print_bda(&bda, pool_only); + + devfile + .seek(SeekFrom::Start(0)) + .map_err(|seek_err| format!("Error during seek: {}", seek_err))?; + + let loaded_state = bda + .load_state(&mut devfile) + .map_err(|stateload_err| format!("Error during load state: {}", stateload_err))?; + + print_pool_metadata(&loaded_state, pool_only)?; + + Ok(()) +} diff --git a/src/bin/tools/mod.rs b/src/bin/tools/mod.rs new file mode 100644 index 0000000000..a2c434ec00 --- /dev/null +++ b/src/bin/tools/mod.rs @@ -0,0 +1,5 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +pub mod dump_metadata; From f5972db5904f67f784a98687dff2ca6a6124e579 Mon Sep 17 00:00:00 2001 From: mulhern Date: Fri, 5 Jan 2024 21:24:10 -0500 Subject: [PATCH 2/2] Improve dispatch for tools in tools package * Change name of primary binary to stratisd-tools * Update CI * Add struct based dispatch * Allow invoking stratisd-tools directly with name as subcommand Signed-off-by: mulhern --- .github/workflows/fedora.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- Cargo.toml | 2 +- Makefile | 16 +++--- src/bin/stratis-dumpmetadata.rs | 88 --------------------------------- src/bin/stratisd-tools.rs | 84 +++++++++++++++++++++++++++++++ src/bin/tools/cmds.rs | 80 ++++++++++++++++++++++++++++++ src/bin/tools/mod.rs | 3 ++ 8 files changed, 180 insertions(+), 97 deletions(-) delete mode 100644 src/bin/stratis-dumpmetadata.rs create mode 100644 src/bin/stratisd-tools.rs create mode 100644 src/bin/tools/cmds.rs diff --git a/.github/workflows/fedora.yml b/.github/workflows/fedora.yml index f0f78b7281..7b13c28235 100644 --- a/.github/workflows/fedora.yml +++ b/.github/workflows/fedora.yml @@ -51,7 +51,7 @@ jobs: - task: PROFILEDIR=debug make -f Makefile build-no-ipc toolchain: 1.75.0 # CURRENT DEVELOPMENT RUST TOOLCHAIN components: cargo - - task: PROFILEDIR=debug make -f Makefile stratis-dumpmetadata + - task: PROFILEDIR=debug make -f Makefile stratisd-tools toolchain: 1.75.0 # CURRENT DEVELOPMENT RUST TOOLCHAIN components: cargo - task: make -f Makefile docs-ci diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 918191ba38..7f72a5551b 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -51,7 +51,7 @@ jobs: - task: PROFILEDIR=debug make -f Makefile build-no-ipc toolchain: 1.75.0 # CURRENT DEVELOPMENT RUST TOOLCHAIN components: cargo - - task: PROFILEDIR=debug make -f Makefile stratis-dumpmetadata + - task: PROFILEDIR=debug make -f Makefile stratisd-tools toolchain: 1.75.0 # CURRENT DEVELOPMENT RUST TOOLCHAIN components: cargo - task: make -f Makefile docs-ci diff --git a/Cargo.toml b/Cargo.toml index 205b075d4d..942cd19646 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ name = "stratisd" required-features = ["engine"] [[bin]] -name = "stratis-dumpmetadata" +name = "stratisd-tools" required-features = ["engine", "extras", "min"] [[bin]] diff --git a/Makefile b/Makefile index d0bb1d7806..0269ede4c1 100644 --- a/Makefile +++ b/Makefile @@ -165,7 +165,7 @@ audit-all-rust: build-all-rust ./target/${PROFILEDIR}/stratis-utils \ ./target/${PROFILEDIR}/stratis-str-cmp \ ./target/${PROFILEDIR}/stratis-base32-decode \ - ./target/${PROFILEDIR}/stratis-dumpmetadata + ./target/${PROFILEDIR}/stratisd-tools ## Check for spelling errors check-typos: @@ -253,12 +253,12 @@ build-stratis-base32-decode: # so we use two distinct targets to build the two binaries build-udev-utils: build-stratis-str-cmp build-stratis-base32-decode -## Build the stratis-dumpmetadata program -stratis-dumpmetadata: +## Build the stratisd-tools program +stratisd-tools: PKG_CONFIG_ALLOW_CROSS=1 \ RUSTFLAGS="${DENY}" \ cargo ${BUILD} ${RELEASE_FLAG} \ - --bin=stratis-dumpmetadata ${EXTRAS_FEATURES} ${TARGET_ARGS} + --bin=stratisd-tools ${EXTRAS_FEATURES} ${TARGET_ARGS} ## Build stratis-min for early userspace stratis-min: @@ -314,8 +314,11 @@ install-binaries: mkdir -p $(DESTDIR)$(BINDIR) mkdir -p $(DESTDIR)$(UNITGENDIR) $(INSTALL) -Dpm0755 -t $(DESTDIR)$(BINDIR) target/$(PROFILEDIR)/stratis-min + + $(INSTALL) -Dpm0755 -t $(DESTDIR)$(BINDIR) target/$(PROFILEDIR)/stratisd-tools + ln --force --verbose $(DESTDIR)$(BINDIR)/stratisd-tools $(DESTDIR)$(BINDIR)/stratis-dumpmetadata + $(INSTALL) -Dpm0755 -t $(DESTDIR)$(BINDIR) target/$(PROFILEDIR)/stratis-utils - $(INSTALL) -Dpm0755 -t $(DESTDIR)$(BINDIR) target/$(PROFILEDIR)/stratis-dumpmetadata mv --force --verbose $(DESTDIR)$(BINDIR)/stratis-utils $(DESTDIR)$(BINDIR)/stratis-predict-usage ln --force --verbose $(DESTDIR)$(BINDIR)/stratis-predict-usage $(DESTDIR)$(UNITGENDIR)/stratis-clevis-setup-generator ln --force --verbose $(DESTDIR)$(BINDIR)/stratis-predict-usage $(DESTDIR)$(UNITGENDIR)/stratis-setup-generator @@ -341,7 +344,7 @@ install-daemons: install: install-udev-cfg install-man-cfg install-dbus-cfg install-dracut-cfg install-systemd-cfg install-binaries install-udev-binaries install-fstab-script install-daemons ## Build all Rust artifacts -build-all-rust: build build-min build-udev-utils stratis-dumpmetadata +build-all-rust: build build-min build-udev-utils stratisd-tools ## Build all man pages build-all-man: docs/stratisd.8 docs/stratis-dumpmetadata.8 @@ -366,6 +369,7 @@ clean-ancillary: rm -fv $(DESTDIR)$(UDEVDIR)/stratis-str-cmp rm -fv $(DESTDIR)$(UDEVDIR)/stratis-base32-decode rm -fv $(DESTDIR)$(BINDIR)/stratis-predict-usage + rm -fv $(DESTDIR)$(BINDIR)/stratisd-tools rm -fv $(DESTDIR)$(BINDIR)/stratis-dumpmetadata rm -fv $(DESTDIR)$(UNITGENDIR)/stratis-setup-generator rm -fv $(DESTDIR)$(UNITGENDIR)/stratis-clevis-setup-generator diff --git a/src/bin/stratis-dumpmetadata.rs b/src/bin/stratis-dumpmetadata.rs deleted file mode 100644 index cd855fb8ae..0000000000 --- a/src/bin/stratis-dumpmetadata.rs +++ /dev/null @@ -1,88 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -mod tools; - -use std::{env, process}; - -use env_logger::Builder; - -use clap::{Arg, ArgAction, Command}; - -use tools::dump_metadata; - -/// Configure and initialize the logger. -/// Read log configuration parameters from the environment if RUST_LOG -/// is set. Otherwise, just accept the default configuration, which is -/// to log at the severity of error only. -fn initialize_log() { - let mut builder = Builder::new(); - - if let Ok(s) = env::var("RUST_LOG") { - builder.parse_filters(&s); - } - - builder.init() -} - -fn parse_args() -> Command { - Command::new("stratis-dumpmetadata") - .next_line_help(true) - .arg( - Arg::new("dev") - .required(true) - .help("Print metadata of given device"), - ) - .arg( - Arg::new("print_bytes") - .long("print-bytes") - .action(ArgAction::SetTrue) - .num_args(0) - .short('b') - .help("Print byte buffer of signature block"), - ) - .arg( - Arg::new("only") - .long("only") - .action(ArgAction::Set) - .value_name("PORTION") - .value_parser(["pool"]) - .help("Only print specified portion of the metadata"), - ) -} - -fn main() { - let matches = parse_args().get_matches(); - let devpath = matches - .get_one::("dev") - .map(|s| s.as_str()) - .expect("'dev' is a mandatory argument"); - - initialize_log(); - - match dump_metadata::run( - devpath, - matches.get_flag("print_bytes"), - matches - .get_one::("only") - .map(|v| v == "pool") - .unwrap_or(false), - ) { - Ok(()) => {} - Err(e) => { - eprintln!("Error encountered: {}", e); - process::exit(1); - } - } -} - -#[cfg(test)] -mod tests { - use super::parse_args; - - #[test] - fn test_dumpmetadata_parse_args() { - parse_args().debug_assert(); - } -} diff --git a/src/bin/stratisd-tools.rs b/src/bin/stratisd-tools.rs new file mode 100644 index 0000000000..281b5a6360 --- /dev/null +++ b/src/bin/stratisd-tools.rs @@ -0,0 +1,84 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +mod tools; + +use std::{env, path::Path, process}; + +use clap::{Arg, Command}; +use env_logger::Builder; + +use crate::tools::cmds; + +fn basename(path: &str) -> Option<&Path> { + Path::new(path).file_name().map(Path::new) +} + +/// Configure and initialize the logger. +/// Read log configuration parameters from the environment if RUST_LOG +/// is set. Otherwise, just accept the default configuration, which is +/// to log at the severity of error only. +fn initialize_log() { + let mut builder = Builder::new(); + + if let Ok(s) = env::var("RUST_LOG") { + builder.parse_filters(&s); + } + + builder.init() +} + +fn main() { + initialize_log(); + + let executable_name = "stratisd-tools"; + + let args = env::args().collect::>(); + let argv1 = &args[0]; + + let stripped_args = if basename(argv1.as_str()) + .map(|n| n == Path::new(executable_name)) + .unwrap_or(false) + { + let command = Command::new(executable_name) + .arg( + Arg::new("executable") + .required(true) + .value_name("EXECUTABLE") + .value_parser(cmds().iter().map(|x| x.name()).collect::>()), + ) + .arg_required_else_help(true); + + let truncated_args = if args.len() > 1 { + vec![argv1, &args[1]] + } else { + vec![argv1] + }; + + command.get_matches_from(truncated_args); + args[1..].to_vec() + } else { + args + }; + + let command_name = match basename(&stripped_args[0]).and_then(|n| n.to_str()) { + Some(name) => name, + None => { + process::exit(1); + } + }; + + if let Some(c) = cmds().iter().find(|x| command_name == x.name()) { + match c.run(stripped_args) { + Ok(()) => {} + Err(e) => { + eprintln!("Error encountered: {e}"); + process::exit(1); + } + } + } else { + eprintln!("Unknown executable name {command_name}"); + process::exit(2); + } +} diff --git a/src/bin/tools/cmds.rs b/src/bin/tools/cmds.rs new file mode 100644 index 0000000000..8f6384a1a7 --- /dev/null +++ b/src/bin/tools/cmds.rs @@ -0,0 +1,80 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use clap::{Arg, ArgAction, Command}; + +use crate::tools::dump_metadata; + +pub trait ToolCommand<'a> { + fn name(&self) -> &'a str; + fn run(&self, command_line_args: Vec) -> Result<(), String>; +} + +struct StratisDumpMetadata; + +impl StratisDumpMetadata { + fn cmd() -> Command { + Command::new("stratis-dumpmetadata") + .next_line_help(true) + .arg( + Arg::new("dev") + .required(true) + .help("Print metadata of given device"), + ) + .arg( + Arg::new("print_bytes") + .long("print-bytes") + .action(ArgAction::SetTrue) + .num_args(0) + .short('b') + .help("Print byte buffer of signature block"), + ) + .arg( + Arg::new("only") + .long("only") + .action(ArgAction::Set) + .value_name("PORTION") + .value_parser(["pool"]) + .help("Only print specified portion of the metadata"), + ) + } +} + +impl<'a> ToolCommand<'a> for StratisDumpMetadata { + fn name(&self) -> &'a str { + "stratis-dumpmetadata" + } + + fn run(&self, command_line_args: Vec) -> Result<(), String> { + let matches = StratisDumpMetadata::cmd().get_matches_from(command_line_args); + let devpath = matches + .get_one::("dev") + .map(|s| s.as_str()) + .expect("'dev' is a mandatory argument"); + + dump_metadata::run( + devpath, + matches.get_flag("print_bytes"), + matches + .get_one::("only") + .map(|v| v == "pool") + .unwrap_or(false), + ) + } +} + +pub fn cmds<'a>() -> Vec>> { + vec![Box::new(StratisDumpMetadata)] +} + +#[cfg(test)] +mod tests { + + use super::StratisDumpMetadata; + + #[test] + fn test_dumpmetadata_parse_args() { + StratisDumpMetadata::cmd().debug_assert(); + } +} diff --git a/src/bin/tools/mod.rs b/src/bin/tools/mod.rs index a2c434ec00..b7d2ac52d2 100644 --- a/src/bin/tools/mod.rs +++ b/src/bin/tools/mod.rs @@ -2,4 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +mod cmds; pub mod dump_metadata; + +pub use cmds::cmds;