From 8fef96ad153f8e2513c72780d396764d97101749 Mon Sep 17 00:00:00 2001 From: mulhern Date: Thu, 5 Dec 2024 08:18:12 -0500 Subject: [PATCH] Add a command-line tool and module for pool inspection At present it is able to print a textual representation of the storage stack, based on the pool-level metadata, and also to run some simple validity checks on that metadata. Signed-off-by: mulhern --- src/bin/tools/check_metadata.rs | 20 + src/bin/tools/cmds.rs | 76 ++- src/bin/tools/mod.rs | 1 + src/engine/mod.rs | 3 + src/engine/strat_engine/mod.rs | 3 + src/engine/strat_engine/pool/inspection.rs | 738 +++++++++++++++++++++ src/engine/strat_engine/pool/mod.rs | 2 + 7 files changed, 840 insertions(+), 3 deletions(-) create mode 100644 src/bin/tools/check_metadata.rs create mode 100644 src/engine/strat_engine/pool/inspection.rs diff --git a/src/bin/tools/check_metadata.rs b/src/bin/tools/check_metadata.rs new file mode 100644 index 0000000000..631881a5bf --- /dev/null +++ b/src/bin/tools/check_metadata.rs @@ -0,0 +1,20 @@ +// 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, path::Path}; + +use stratisd::engine::pool_inspection::inspectors; + +pub fn run(infile: &Path, print: bool) -> Result<(), String> { + let metadata_str = fs::read_to_string(infile) + .map_err(|the_io_error| format!("Error opening file: {}", the_io_error))?; + let metadata = serde_json::from_str(&metadata_str) + .map_err(|the_json_error| format!("Error parsing json into structs: {}", the_json_error))?; + + if print { + inspectors::print(&metadata).map_err(|the_error| format!("Error: {}", the_error)) + } else { + inspectors::check(&metadata).map_err(|the_error| format!("Error: {}", the_error)) + } +} diff --git a/src/bin/tools/cmds.rs b/src/bin/tools/cmds.rs index 790fe9c6de..500acc2bca 100644 --- a/src/bin/tools/cmds.rs +++ b/src/bin/tools/cmds.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use clap::{Arg, ArgAction, Command}; -use crate::tools::dump_metadata; +use crate::tools::{check_metadata, dump_metadata}; use stratisd::stratis::VERSION; @@ -70,17 +70,87 @@ impl<'a> ToolCommand<'a> for StratisDumpMetadata { } } +struct StratisCheckMetadata; + +impl StratisCheckMetadata { + fn cmd() -> Command { + Command::new("stratis-checkmetadata") + .version(VERSION) + .about("Check validity of Stratis metadata") + .next_line_help(true) + .arg( + Arg::new("file") + .value_parser(clap::value_parser!(PathBuf)) + .required(true) + .help("File containing pool-level metadata as JSON"), + ) + } +} + +impl<'a> ToolCommand<'a> for StratisCheckMetadata { + fn name(&self) -> &'a str { + "stratis-checkmetadata" + } + + fn run(&self, command_line_args: Vec) -> Result<(), String> { + let matches = StratisCheckMetadata::cmd().get_matches_from(command_line_args); + let infile = matches + .get_one::("file") + .expect("'file' is a mandatory argument"); + + check_metadata::run(infile, false) + } +} + +struct StratisPrintMetadata; + +impl StratisPrintMetadata { + fn cmd() -> Command { + Command::new("stratis-printmetadata") + .version(VERSION) + .about("Print a human-suitable representation of Stratis metadata") + .next_line_help(true) + .arg( + Arg::new("file") + .value_parser(clap::value_parser!(PathBuf)) + .required(true) + .help("File containing pool-level metadata as JSON"), + ) + } +} + +impl<'a> ToolCommand<'a> for StratisPrintMetadata { + fn name(&self) -> &'a str { + "stratis-printmetadata" + } + + fn run(&self, command_line_args: Vec) -> Result<(), String> { + let matches = StratisPrintMetadata::cmd().get_matches_from(command_line_args); + let infile = matches + .get_one::("file") + .expect("'file' is a mandatory argument"); + + check_metadata::run(infile, true) + } +} + pub fn cmds<'a>() -> Vec>> { - vec![Box::new(StratisDumpMetadata)] + vec![ + Box::new(StratisCheckMetadata), + Box::new(StratisDumpMetadata), + Box::new(StratisPrintMetadata), + ] } #[cfg(test)] mod tests { - use super::StratisDumpMetadata; + use super::{StratisCheckMetadata, StratisDumpMetadata, StratisPrintMetadata}; #[test] fn test_dumpmetadata_parse_args() { + StratisCheckMetadata::cmd().debug_assert(); StratisDumpMetadata::cmd().debug_assert(); + StratisPrintMetadata::cmd().debug_assert(); } } diff --git a/src/bin/tools/mod.rs b/src/bin/tools/mod.rs index 06f90e0b4e..a7d3a25f57 100644 --- a/src/bin/tools/mod.rs +++ b/src/bin/tools/mod.rs @@ -2,6 +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 check_metadata; mod cmds; mod dump_metadata; diff --git a/src/engine/mod.rs b/src/engine/mod.rs index c0a8260e65..17396d5fc2 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -5,6 +5,9 @@ #[cfg(feature = "test_extras")] pub use self::strat_engine::{ProcessedPathInfos, StratPool}; +#[cfg(feature = "extras")] +pub use self::strat_engine::pool_inspection; + pub use self::{ engine::{BlockDev, Engine, Filesystem, KeyActions, Pool, Report}, shared::{total_allocated, total_used}, diff --git a/src/engine/strat_engine/mod.rs b/src/engine/strat_engine/mod.rs index f9520dd0a6..37fd872abf 100644 --- a/src/engine/strat_engine/mod.rs +++ b/src/engine/strat_engine/mod.rs @@ -25,6 +25,9 @@ mod writing; #[cfg(feature = "test_extras")] pub use self::{backstore::ProcessedPathInfos, pool::v1::StratPool}; +#[cfg(feature = "extras")] +pub use self::pool::inspection as pool_inspection; + pub use self::{ backstore::integrity_meta_space, crypt::{ diff --git a/src/engine/strat_engine/pool/inspection.rs b/src/engine/strat_engine/pool/inspection.rs new file mode 100644 index 0000000000..c4b542735d --- /dev/null +++ b/src/engine/strat_engine/pool/inspection.rs @@ -0,0 +1,738 @@ +// 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::{ + collections::{hash_map::Entry, HashMap}, + fmt, +}; + +use devicemapper::Sectors; + +use crate::{ + engine::{strat_engine::serde_structs::PoolSave, types::DevUuid}, + stratis::{StratisError, StratisResult}, +}; + +const SIZE_OF_CRYPT_METADATA_SECTORS: Sectors = Sectors(32768); +const SIZE_OF_STRATIS_METADATA_SECTORS: Sectors = Sectors(8192); + +// Encodes the use for each extent. +trait Use: Copy + Eq + PartialEq + fmt::Display {} + +trait Allocator { + // How to mark an unused portion of the device. + fn unused_marker() -> U; + + // Offset from the start of the device to perform calculations. + fn offset(&self) -> Sectors; + + // The recorded extents of the offset + fn extents(&self) -> &HashMap; + + // A table using the unused marker for unused extents + fn filled(&self) -> HashMap { + filled(self.extents(), Self::unused_marker(), self.offset()) + } + + // The sum of the lengths of all the extents that belong to any of the + // uses. + fn sum(&self, uses: &[U]) -> Sectors { + sum(&self.filled(), uses) + } +} + +// Return the sum of the length of all the extents in extents that fall into +// any of the list of uses. An empty list of uses will always result in a sum +// of 0 sectors. +fn sum(extents: &HashMap, uses: &[U]) -> Sectors +where + U: Use, +{ + extents + .values() + .filter_map(|(u, l)| if uses.contains(u) { Some(l) } else { None }) + .cloned() + .sum() +} + +// Returns a map of start sectors to the extents use and length. +// Marks unused parts with the specified filler use. +// Begins at start_offset which must be at least 0, but may be more. +fn filled( + extents: &HashMap, + filler: U, + start_offset: Sectors, +) -> HashMap +where + U: Use, +{ + let mut result = HashMap::new(); + let mut current_offset = start_offset; + let mut starts: Vec<&Sectors> = extents.keys().collect(); + starts.sort(); + + for &start in starts { + let (used, length) = extents[&start]; + if start > current_offset { + result.insert(start, (filler, start - current_offset)); + } + result.insert(start, (used, length)); + current_offset = start + length; + } + + result +} + +// Add an optional vector of extents to the current data structure. +fn add( + current: &mut HashMap, + to_add: &[(Sectors, Sectors)], + used: U, +) -> StratisResult<()> +where + U: Use, +{ + for (start, length) in to_add.iter() { + if current.contains_key(start) { + return Err(StratisError::Msg(format!( + "Key collision: {start} already in extents table" + ))); + } + current.insert(*start, (used, *length)); + } + + Ok(()) +} + +// Print a representation of extents for display. +fn display(f: &mut fmt::Formatter<'_>, extents: &HashMap) -> fmt::Result +where + U: Use, +{ + let mut starts: Vec<&Sectors> = extents.keys().collect(); + starts.sort(); + for (index, &&start) in starts.iter().enumerate() { + let (used, length) = extents[&start]; + let end = start + length; + let (start, length, end) = (*start, *length, *end); + writeln!( + f, + "{index}: Use: {used:20} {start:12} + {length:12} = {end:12} sectors" + )?; + } + Ok(()) +} + +// Check whether any extent overlaps with another. +fn check_overlap(extents: &HashMap, start_offset: Sectors) -> Vec +where + U: Use, +{ + let mut errors = vec![]; + let mut current_offset = start_offset; + let mut starts: Vec<&Sectors> = extents.keys().collect(); + starts.sort(); + + for &start in starts { + let (used, length) = extents[&start]; + if start < current_offset { + errors.push(format!("allocation ({start}, {length}) for {used} overlaps with previous allocation which extends to {current_offset}")) + } + current_offset = start + length; + } + + errors +} + +#[derive(strum_macros::Display)] +#[strum(serialize_all = "snake_case")] +#[derive(Clone, Copy, Eq, PartialEq)] +enum CapDeviceUse { + Allocated, + Unused, +} + +impl Use for CapDeviceUse {} + +struct CapDevice { + extents: HashMap, + encrypted: bool, +} + +impl CapDevice { + fn new(encrypted: bool) -> CapDevice { + CapDevice { + extents: HashMap::new(), + encrypted, + } + } + + fn add(mut self, allocs: Option<&[(Sectors, Sectors)]>) -> StratisResult { + if let Some(allocs) = allocs { + add(&mut self.extents, allocs, CapDeviceUse::Allocated)?; + } + + Ok(self) + } + + fn check(&self) -> Vec { + let mut errors = Vec::new(); + errors.extend(check_overlap(&self.extents, self.offset())); + errors + } +} + +impl Allocator for CapDevice { + fn offset(&self) -> Sectors { + if self.encrypted { + Sectors(0) + } else { + SIZE_OF_CRYPT_METADATA_SECTORS + } + } + + fn unused_marker() -> CapDeviceUse { + CapDeviceUse::Unused + } + + fn extents(&self) -> &HashMap { + &self.extents + } +} + +impl fmt::Display for CapDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display(f, &self.filled()) + } +} + +#[derive(strum_macros::Display)] +#[strum(serialize_all = "snake_case")] +#[derive(Clone, Copy, Eq, PartialEq)] +enum DataDeviceUse { + StratisMetadata, + IntegrityMetadata, + Allocated, + Unused, +} + +impl Use for DataDeviceUse {} + +struct DataDevice { + extents: HashMap, +} + +impl DataDevice { + fn new() -> DataDevice { + let mut extents = HashMap::new(); + extents.insert( + Sectors(0), + ( + DataDeviceUse::StratisMetadata, + SIZE_OF_STRATIS_METADATA_SECTORS, + ), + ); + DataDevice { extents } + } + + fn add( + mut self, + integrity_meta_allocs: Option<&Vec<(Sectors, Sectors)>>, + allocs: Option<&[(Sectors, Sectors)]>, + ) -> StratisResult { + if let Some(allocs) = integrity_meta_allocs { + add(&mut self.extents, allocs, DataDeviceUse::IntegrityMetadata)?; + } + + if let Some(allocs) = allocs { + add(&mut self.extents, allocs, DataDeviceUse::Allocated)?; + } + + Ok(self) + } + + fn _check_integrity_meta_round(&self) -> Vec { + let mut errors = Vec::new(); + + for (_, &(_, length)) in self + .extents + .iter() + .filter(|(_, &(used, _))| used == DataDeviceUse::IntegrityMetadata) + { + if length % Sectors(8) != Sectors(0) { + errors.push(format!( + "Allocation {length} for integrity meta data not a multiple of 4KiB" + )); + } + } + + errors + } + + fn check(&self) -> Vec { + let mut errors = Vec::new(); + errors.extend(check_overlap(&self.extents, self.offset())); + errors.extend(self._check_integrity_meta_round()); + errors + } +} + +impl Allocator for DataDevice { + #[allow(clippy::unused_self)] + fn offset(&self) -> Sectors { + Sectors(0) + } + + fn unused_marker() -> DataDeviceUse { + DataDeviceUse::Unused + } + + fn extents(&self) -> &HashMap { + &self.extents + } +} + +impl fmt::Display for DataDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display(f, &self.filled()) + } +} + +#[derive(strum_macros::Display)] +#[strum(serialize_all = "snake_case")] +#[derive(Clone, Copy, Eq, PartialEq)] +enum CacheDeviceUse { + StratisMetadata, + CacheMetadata, + CacheData, + Unused, +} + +impl Use for CacheDeviceUse {} + +struct CacheDevice { + extents: HashMap, +} + +impl CacheDevice { + fn new() -> CacheDevice { + let mut extents = HashMap::new(); + extents.insert( + Sectors(0), + ( + CacheDeviceUse::StratisMetadata, + SIZE_OF_STRATIS_METADATA_SECTORS, + ), + ); + CacheDevice { extents } + } + + fn add( + mut self, + metadata_allocs: Option<&[(Sectors, Sectors)]>, + data_allocs: Option<&[(Sectors, Sectors)]>, + ) -> StratisResult { + if let Some(allocs) = metadata_allocs { + add(&mut self.extents, allocs, CacheDeviceUse::CacheMetadata)?; + } + + if let Some(allocs) = data_allocs { + add(&mut self.extents, allocs, CacheDeviceUse::CacheData)?; + } + + Ok(self) + } + + fn check(&self) -> Vec { + let mut errors = Vec::new(); + errors.extend(check_overlap(&self.extents, self.offset())); + errors + } +} + +impl Allocator for CacheDevice { + #[allow(clippy::unused_self)] + fn offset(&self) -> Sectors { + Sectors(0) + } + + fn unused_marker() -> CacheDeviceUse { + CacheDeviceUse::Unused + } + + fn extents(&self) -> &HashMap { + &self.extents + } +} + +impl fmt::Display for CacheDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display(f, &self.filled()) + } +} + +#[derive(strum_macros::Display)] +#[strum(serialize_all = "snake_case")] +#[derive(Clone, Copy, Eq, PartialEq)] +enum CryptAllocsUse { + Metadata, +} + +impl Use for CryptAllocsUse {} + +struct CryptAllocs { + extents: HashMap, +} + +impl CryptAllocs { + fn new() -> CryptAllocs { + CryptAllocs { + extents: HashMap::new(), + } + } + + fn add(mut self, allocs: Option<&Vec<(Sectors, Sectors)>>) -> StratisResult { + if let Some(allocs) = allocs { + add(&mut self.extents, allocs, CryptAllocsUse::Metadata)?; + } + + Ok(self) + } + + fn check(&self) -> Vec { + let mut errors = vec![]; + + if self.extents.is_empty() { + errors.push("No allocations for crypt metadata".into()); + } + + if self.extents.len() > 1 { + errors.push("Multiple allocations for crypt metadata".into()); + } + + let (&start, &(_, length)) = self + .extents + .iter() + .collect::>() + .pop() + .expect("Exactly one extents in the extent map"); + + if start != Sectors(0) { + errors.push(format!("Crypt meta allocs offset, {start} is not 0")); + } + + if length != SIZE_OF_CRYPT_METADATA_SECTORS { + errors.push(format!( + "Crypt meta allocs entry has unexpected length {length}" + )); + } + + errors + } +} + +impl fmt::Display for CryptAllocs { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display(f, &self.extents) + } +} + +#[derive(strum_macros::Display)] +#[strum(serialize_all = "snake_case")] +#[derive(Clone, Copy, Eq, PartialEq)] +enum FlexDeviceUse { + MetaDev, + ThinDataDev, + ThinMetaDev, + ThinMetaDevSpare, + Unused, +} + +impl Use for FlexDeviceUse {} + +struct FlexDevice { + extents: HashMap, + encrypted: bool, +} + +impl FlexDevice { + fn new(encrypted: bool) -> FlexDevice { + FlexDevice { + extents: HashMap::new(), + encrypted, + } + } + + fn add( + mut self, + thin_meta_dev: Option<&Vec<(Sectors, Sectors)>>, + thin_meta_dev_spare: Option<&Vec<(Sectors, Sectors)>>, + meta_dev: Option<&Vec<(Sectors, Sectors)>>, + thin_data_dev: Option<&Vec<(Sectors, Sectors)>>, + ) -> StratisResult { + if let Some(allocs) = thin_meta_dev { + add(&mut self.extents, allocs, FlexDeviceUse::ThinMetaDev)?; + } + + if let Some(allocs) = thin_meta_dev_spare { + add(&mut self.extents, allocs, FlexDeviceUse::ThinMetaDevSpare)?; + } + + if let Some(allocs) = meta_dev { + add(&mut self.extents, allocs, FlexDeviceUse::MetaDev)?; + } + + if let Some(allocs) = thin_data_dev { + add(&mut self.extents, allocs, FlexDeviceUse::ThinDataDev)?; + } + + Ok(self) + } + + // Verify that both thin meta devices, the one currently in use and the + // spare, are the same size. + fn _check_thin_metas_equal(&self) -> Vec { + let thin_meta_total = self.sum(&[FlexDeviceUse::ThinMetaDev]); + let thin_meta_spare_total = self.sum(&[FlexDeviceUse::ThinMetaDevSpare]); + if thin_meta_total == thin_meta_spare_total { + vec![] + } else { + vec![format!("The sum of the allocations for the thin meta device, {thin_meta_total}, does not equal the sum of the allocations for the thin meta spare device, {thin_meta_spare_total}.")] + } + } + + fn check(&self) -> Vec { + let mut errors = Vec::new(); + errors.extend(self._check_thin_metas_equal()); + errors.extend(check_overlap(&self.extents, self.offset())); + errors + } +} + +impl Allocator for FlexDevice { + fn offset(&self) -> Sectors { + if self.encrypted { + Sectors(0) + } else { + SIZE_OF_CRYPT_METADATA_SECTORS + } + } + + fn unused_marker() -> FlexDeviceUse { + FlexDeviceUse::Unused + } + + fn extents(&self) -> &HashMap { + &self.extents + } +} + +impl fmt::Display for FlexDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + display(f, &self.filled()) + } +} + +// Calculate map of device UUIDs to data device representation from metadata. +fn data_devices(metadata: &PoolSave) -> StratisResult> { + let data_tier_metadata = &metadata.backstore.data_tier; + + let data_tier_devs = &data_tier_metadata.blockdev.devs; + + let mut bds = data_tier_devs + .iter() + .try_fold(HashMap::new(), |mut acc, dev| { + if let Entry::Vacant(e) = acc.entry(dev.uuid) { + e.insert(DataDevice::new().add(Some(&dev.integrity_meta_allocs), None)?); + Ok(acc) + } else { + Err(StratisError::Msg(format!( + "Two devices with same UUID {} in devs structure", + dev.uuid + ))) + } + })?; + + let data_tier_allocs = &data_tier_metadata.blockdev.allocs[0]; + + for item in data_tier_allocs { + if let Some(dd) = bds.remove(&item.parent) { + bds.insert( + item.parent, + dd.add(None, Some(&[(item.start, item.length)]))?, + ); + } else { + return Err(StratisError::Msg(format!( + "No device in devs for uuid {} in blockdevs", + item.parent + ))); + } + } + + Ok(bds) +} + +// Calculate map of device UUIDs to cache device representation from metadata. +fn cache_devices(metadata: &PoolSave) -> StratisResult> { + let cache_tier_metadata = &metadata.backstore.cache_tier; + + cache_tier_metadata.as_ref().map_or_else( + || Ok(HashMap::new()), + |cache_tier_metadata| { + let cache_tier_devs = &cache_tier_metadata.blockdev.devs; + let mut bds = cache_tier_devs + .iter() + .try_fold(HashMap::new(), |mut acc, dev| { + if let Entry::Vacant(e) = acc.entry(dev.uuid) { + e.insert(CacheDevice::new()); + Ok(acc) + } else { + Err(StratisError::Msg(format!( + "Two devices with same UUID {} in devs structure", + dev.uuid + ))) + } + })?; + + let cache_tier_allocs = &cache_tier_metadata.blockdev.allocs; + + for item in &cache_tier_allocs[0] { + if let Some(dd) = bds.remove(&item.parent) { + bds.insert( + item.parent, + dd.add(Some(&[(item.start, item.length)]), None)?, + ); + } else { + return Err(StratisError::Msg(format!( + "No device in devs for uuid {} in blockdevs", + item.parent + ))); + } + } + + for item in &cache_tier_allocs[1] { + if let Some(dd) = bds.remove(&item.parent) { + bds.insert( + item.parent, + dd.add(None, Some(&[(item.start, item.length)]))?, + ); + } else { + return Err(StratisError::Msg(format!( + "No device in devs for uuid {} in blockdevs", + item.parent + ))); + } + } + + Ok(bds) + }, + ) +} + +// Calculate allocations for crypt metadata from the metadata. +fn crypt_allocs(metadata: &PoolSave) -> StratisResult { + let crypt_metadata = &metadata.backstore.cap.crypt_meta_allocs; + + CryptAllocs::new().add(Some(crypt_metadata)) +} + +// Calculate the flex device from the metadata. +fn flex_device(metadata: &PoolSave, encrypted: bool) -> StratisResult { + let flex_device_metadata = &metadata.flex_devs; + + FlexDevice::new(encrypted).add( + Some(&flex_device_metadata.thin_meta_dev), + Some(&flex_device_metadata.thin_meta_dev_spare), + Some(&flex_device_metadata.meta_dev), + Some(&flex_device_metadata.thin_data_dev), + ) +} + +fn cap_device(metadata: &PoolSave, encrypted: bool) -> StratisResult { + let cap_device_metadata = &metadata.backstore.cap; + + CapDevice::new(encrypted).add(Some(&cap_device_metadata.allocs)) +} + +/// Some ways of inspecting the pool-level metadata. +pub mod inspectors { + use super::{ + cache_devices, cap_device, crypt_allocs, data_devices, flex_device, PoolSave, StratisResult, + }; + + use crate::{engine::strat_engine::serde_structs::PoolFeatures, stratis::StratisError}; + + /// Check that the metadata is well-formed. + pub fn check(metadata: &PoolSave) -> StratisResult<()> { + let mut errors = Vec::new(); + + let encrypted = metadata.features.contains(&PoolFeatures::Encryption); + + let data_devices = data_devices(metadata)?; + for data_device in data_devices.values() { + errors.extend(data_device.check()); + } + + let cache_devices = cache_devices(metadata)?; + for cache_device in cache_devices.values() { + errors.extend(cache_device.check()); + } + + let crypt_allocs = crypt_allocs(metadata)?; + errors.extend(crypt_allocs.check()); + + let cap_device = cap_device(metadata, encrypted)?; + errors.extend(cap_device.check()); + + let flex_device = flex_device(metadata, encrypted)?; + errors.extend(flex_device.check()); + + if !errors.is_empty() { + Err(StratisError::Msg(errors.join("\n"))) + } else { + Ok(()) + } + } + + /// Print a human-useful representation of the metadata's meaning. + pub fn print(metadata: &PoolSave) -> StratisResult<()> { + let encrypted = metadata.features.contains(&PoolFeatures::Encryption); + + let crypt_allocs = crypt_allocs(metadata)?; + let flex_device = flex_device(metadata, encrypted)?; + let data_devices = data_devices(metadata)?; + let cache_devices = cache_devices(metadata)?; + let cap_device = cap_device(metadata, encrypted)?; + + println!("Allocations from each data device:"); + for (uuid, bd) in data_devices.iter() { + println!("Data device: {uuid}"); + println!("{}", bd); + } + + println!(); + + println!("Allocations from each cache device:"); + for (uuid, bd) in cache_devices.iter() { + println!("Cache device: {uuid}"); + println!("{}", bd); + } + + println!(); + + println!("Allocations for crypt metadata:"); + print!("{}", crypt_allocs); + + println!(); + + println!("Allocations from cap device:"); + println!("{}", cap_device); + + println!(); + + println!("Allocations from flex device:"); + print!("{}", flex_device); + Ok(()) + } +} diff --git a/src/engine/strat_engine/pool/mod.rs b/src/engine/strat_engine/pool/mod.rs index db80d738c7..793469a5d8 100644 --- a/src/engine/strat_engine/pool/mod.rs +++ b/src/engine/strat_engine/pool/mod.rs @@ -3,6 +3,8 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. mod dispatch; +#[cfg(feature = "extras")] +pub mod inspection; pub mod v1; pub mod v2;