From e9fbcc31de7b8a8501bb80d3620040d90a7eeee3 Mon Sep 17 00:00:00 2001 From: Joe Thornber Date: Mon, 28 Sep 2020 15:42:07 +0100 Subject: [PATCH] [thin_dump (rust)] First pass at thin_dump. Doesn't include --repair. This includes and sections for shared regions. --- src/bin/thin_dump.rs | 105 ++++++++++++++ src/pdata/btree.rs | 29 +++- src/pdata/space_map.rs | 36 +++++ src/shrink/toplevel.rs | 24 +++ src/thin/check.rs | 8 + src/thin/dump.rs | 323 +++++++++++++++++++++++++++++++++++++++++ src/thin/mod.rs | 1 + src/thin/xml.rs | 37 +++++ tests/thin_shrink.rs | 12 ++ 9 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 src/bin/thin_dump.rs create mode 100644 src/thin/dump.rs diff --git a/src/bin/thin_dump.rs b/src/bin/thin_dump.rs new file mode 100644 index 0000000..3954249 --- /dev/null +++ b/src/bin/thin_dump.rs @@ -0,0 +1,105 @@ +extern crate clap; +extern crate thinp; + +use atty::Stream; +use clap::{App, Arg}; +use std::path::Path; +use std::process; +use std::process::exit; +use std::sync::Arc; +use thinp::file_utils; +use thinp::report::*; +use thinp::thin::dump::{dump, ThinDumpOptions}; + +fn main() { + let parser = App::new("thin_check") + .version(thinp::version::TOOLS_VERSION) + .about("Validates thin provisioning metadata on a device or file.") + .arg( + Arg::with_name("QUIET") + .help("Suppress output messages, return only exit code.") + .short("q") + .long("quiet"), + ) + .arg( + Arg::with_name("SB_ONLY") + .help("Only check the superblock.") + .long("super-block-only") + .value_name("SB_ONLY"), + ) + .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), + ) + .arg( + Arg::with_name("SYNC_IO") + .help("Force use of synchronous io") + .long("sync-io"), + ); + + let matches = parser.get_matches(); + let input_file = Path::new(matches.value_of("INPUT").unwrap()); + + if !file_utils::file_exists(input_file) { + eprintln!("Couldn't find input file '{:?}'.", &input_file); + exit(1); + } + + let report; + + if matches.is_present("QUIET") { + report = std::sync::Arc::new(mk_quiet_report()); + } else if atty::is(Stream::Stdout) { + report = std::sync::Arc::new(mk_progress_bar_report()); + } else { + report = Arc::new(mk_simple_report()); + } + + let opts = ThinDumpOptions { + dev: &input_file, + async_io: !matches.is_present("SYNC_IO"), + report, + }; + + if let Err(reason) = dump(opts) { + println!("{}", reason); + process::exit(1); + } +} diff --git a/src/pdata/btree.rs b/src/pdata/btree.rs index f491a66..36f6205 100644 --- a/src/pdata/btree.rs +++ b/src/pdata/btree.rs @@ -550,6 +550,11 @@ pub trait NodeVisitor { values: &[V], ) -> Result<()>; + // Nodes may be shared and thus visited multiple times. The walker avoids + // doing repeated IO, but it does call this method to keep the visitor up to + // date. + fn visit_again(&self, path: &Vec, b: u64) -> Result<()>; + fn end_walk(&self) -> Result<()>; } @@ -654,7 +659,11 @@ impl BTreeWalker { // This node has already been checked ... match self.failed(bs[i]) { None => { - // ... it was clean so we can ignore. + // ... it was clean. + if let Err(e) = visitor.visit_again(path, bs[i]) { + // ... but the visitor isn't happy + errs.push(e.clone()); + } } Some(e) => { // ... there was an error @@ -773,7 +782,7 @@ impl BTreeWalker { if let Some(e) = self.failed(root) { Err(e.clone()) } else { - Ok(()) + visitor.visit_again(path, root) } } else { let root = self.engine.read(root).map_err(|_| io_err(path))?; @@ -878,7 +887,11 @@ where // This node has already been checked ... match w.failed(bs[i]) { None => { - // ... it was clean so we can ignore. + // ... it was clean. + if let Err(e) = visitor.visit_again(path, bs[i]) { + // ... but the visitor isn't happy + errs.push(e.clone()); + } } Some(e) => { // ... there was an error @@ -953,7 +966,7 @@ where if let Some(e) = w.failed(root) { Err(e.clone()) } else { - Ok(()) + visitor.visit_again(path, root) } } else { let root = w.engine.read(root).map_err(|_| io_err(path))?; @@ -997,6 +1010,10 @@ impl NodeVisitor for ValueCollector { Ok(()) } + fn visit_again(&self, _path: &Vec, _b: u64) -> Result<()> { + Ok(()) + } + fn end_walk(&self) -> Result<()> { Ok(()) } @@ -1060,6 +1077,10 @@ impl NodeVisitor for ValuePathCollector { Ok(()) } + fn visit_again(&self, _path: &Vec, _b: u64) -> Result<()> { + Ok(()) + } + fn end_walk(&self) -> Result<()> { Ok(()) } diff --git a/src/pdata/space_map.rs b/src/pdata/space_map.rs index 6fd52fd..fa0af19 100644 --- a/src/pdata/space_map.rs +++ b/src/pdata/space_map.rs @@ -225,6 +225,10 @@ pub trait SpaceMap { fn get_nr_blocks(&self) -> Result; fn get_nr_allocated(&self) -> Result; fn get(&self, b: u64) -> Result; + + // Returns the old ref count + fn set(&mut self, b: u64, v: u32) -> Result; + fn inc(&mut self, begin: u64, len: u64) -> Result<()>; } @@ -265,6 +269,20 @@ where Ok(self.counts[b as usize].into()) } + fn set(&mut self, b: u64, v: u32) -> Result { + let old = self.counts[b as usize]; + assert!(v < 0xff); // FIXME: we can't assume this + self.counts[b as usize] = V::from(v as u8); + + if old == V::from(0u8) && v != 0 { + self.nr_allocated += 1; + } else if old != V::from(0u8) && v == 0 { + self.nr_allocated -= 1; + } + + Ok(old.into()) + } + fn inc(&mut self, begin: u64, len: u64) -> Result<()> { for b in begin..(begin + len) { if self.counts[b as usize] == V::from(0u8) { @@ -325,6 +343,24 @@ impl SpaceMap for RestrictedSpaceMap { } } + fn set(&mut self, b: u64, v: u32) -> Result { + let old = self.counts.contains(b as usize); + + if v > 0 { + if !old { + self.nr_allocated += 1; + } + self.counts.insert(b as usize); + } else { + if old { + self.nr_allocated -= 1; + } + self.counts.set(b as usize, false); + } + + Ok(if old {1} else {0}) + } + fn inc(&mut self, begin: u64, len: u64) -> Result<()> { for b in begin..(begin + len) { if !self.counts.contains(b as usize) { diff --git a/src/shrink/toplevel.rs b/src/shrink/toplevel.rs index 1a3d8a9..e7cde54 100644 --- a/src/shrink/toplevel.rs +++ b/src/shrink/toplevel.rs @@ -45,6 +45,14 @@ impl xml::MetadataVisitor for Pass1 { Ok(Visit::Continue) } + fn def_shared_b(&mut self, _name: &str) -> Result { + todo!(); + } + + fn def_shared_e(&mut self) -> Result { + todo!(); + } + fn device_b(&mut self, _d: &xml::Device) -> Result { Ok(Visit::Continue) } @@ -63,6 +71,10 @@ impl xml::MetadataVisitor for Pass1 { Ok(Visit::Continue) } + fn ref_shared(&mut self, _name: &str) -> Result { + todo!(); + } + fn eof(&mut self) -> Result { Ok(Visit::Continue) } @@ -96,6 +108,14 @@ impl xml::MetadataVisitor for Pass2 { self.writer.superblock_e() } + fn def_shared_b(&mut self, _name: &str) -> Result { + todo!(); + } + + fn def_shared_e(&mut self) -> Result { + todo!(); + } + fn device_b(&mut self, d: &xml::Device) -> Result { self.writer.device_b(d) } @@ -127,6 +147,10 @@ impl xml::MetadataVisitor for Pass2 { Ok(Visit::Continue) } + fn ref_shared(&mut self, _name: &str) -> Result { + todo!(); + } + fn eof(&mut self) -> Result { self.writer.eof() } diff --git a/src/thin/check.rs b/src/thin/check.rs index 71ce195..e95b81b 100644 --- a/src/thin/check.rs +++ b/src/thin/check.rs @@ -59,6 +59,10 @@ impl NodeVisitor for BottomLevelVisitor { Ok(()) } + fn visit_again(&self, _path: &Vec, _b: u64) -> btree::Result<()> { + Ok(()) + } + fn end_walk(&self) -> btree::Result<()> { Ok(()) } @@ -98,6 +102,10 @@ impl<'a> NodeVisitor for OverflowChecker<'a> { Ok(()) } + fn visit_again(&self, _path: &Vec, _b: u64) -> btree::Result<()> { + Ok(()) + } + fn end_walk(&self) -> btree::Result<()> { Ok(()) } diff --git a/src/thin/dump.rs b/src/thin/dump.rs new file mode 100644 index 0000000..2de6cd4 --- /dev/null +++ b/src/thin/dump.rs @@ -0,0 +1,323 @@ +use anyhow::Result; +use std::collections::{BTreeMap, BTreeSet}; +use std::path::Path; +use std::sync::{Arc, Mutex}; + +use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine}; +use crate::pdata::btree::{self, *}; +use crate::pdata::space_map::*; +use crate::pdata::unpack::*; +use crate::report::*; +use crate::thin::block_time::*; +use crate::thin::device_detail::*; +use crate::thin::superblock::*; +use crate::thin::xml::{self, MetadataVisitor}; + +//------------------------------------------ + +struct RunBuilder { + run: Option, +} + +impl RunBuilder { + fn new() -> RunBuilder { + RunBuilder { run: None } + } + + fn next(&mut self, thin_block: u64, data_block: u64, time: u32) -> Option { + use xml::Map; + + match self.run { + None => { + self.run = Some(xml::Map { + thin_begin: thin_block, + data_begin: data_block, + time: time, + len: 1, + }); + None + } + Some(xml::Map { + thin_begin, + data_begin, + time: mtime, + len, + }) => { + if thin_block == (thin_begin + len) + && data_block == (data_begin + len) + && mtime == time + { + self.run.as_mut().unwrap().len += 1; + None + } else { + self.run.replace(Map { + thin_begin: thin_block, + data_begin: data_block, + time: time, + len: 1, + }) + } + } + } + } + + fn complete(&mut self) -> Option { + self.run.take() + } +} + +//------------------------------------------ + +struct MVInner<'a> { + md_out: &'a mut dyn xml::MetadataVisitor, + builder: RunBuilder, +} + +struct MappingVisitor<'a> { + inner: Mutex>, +} + +//------------------------------------------ + +impl<'a> MappingVisitor<'a> { + fn new(md_out: &'a mut dyn xml::MetadataVisitor) -> MappingVisitor<'a> { + MappingVisitor { + inner: Mutex::new(MVInner { + md_out, + builder: RunBuilder::new(), + }), + } + } +} + +impl<'a> NodeVisitor for MappingVisitor<'a> { + fn visit( + &self, + _path: &Vec, + _kr: &KeyRange, + _h: &NodeHeader, + keys: &[u64], + values: &[BlockTime], + ) -> btree::Result<()> { + let mut inner = self.inner.lock().unwrap(); + for (k, v) in keys.iter().zip(values.iter()) { + if let Some(run) = inner.builder.next(*k, v.block, v.time) { + inner + .md_out + .map(&run) + .map_err(|e| btree::value_err(format!("{}", e)))?; + } + } + + Ok(()) + } + + fn visit_again(&self, _path: &Vec, b: u64) -> btree::Result<()> { + let mut inner = self.inner.lock().unwrap(); + inner + .md_out + .ref_shared(&format!("{}", b)) + .map_err(|e| btree::value_err(format!("{}", e)))?; + Ok(()) + } + + fn end_walk(&self) -> btree::Result<()> { + let mut inner = self.inner.lock().unwrap(); + if let Some(run) = inner.builder.complete() { + inner + .md_out + .map(&run) + .map_err(|e| btree::value_err(format!("{}", e)))?; + } + Ok(()) + } +} + +//------------------------------------------ + +const MAX_CONCURRENT_IO: u32 = 1024; + +pub struct ThinDumpOptions<'a> { + pub dev: &'a Path, + pub async_io: bool, + pub report: Arc, +} + +struct Context { + report: Arc, + engine: Arc, +} + +fn mk_context(opts: &ThinDumpOptions) -> Result { + let engine: Arc; + + if opts.async_io { + engine = Arc::new(AsyncIoEngine::new(opts.dev, 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)?); + } + + Ok(Context { + report: opts.report.clone(), + engine, + }) +} + +//------------------------------------------ + +struct NoopVisitor {} + +impl btree::NodeVisitor for NoopVisitor { + fn visit( + &self, + _path: &Vec, + _kr: &btree::KeyRange, + _h: &btree::NodeHeader, + _k: &[u64], + _values: &[V], + ) -> btree::Result<()> { + Ok(()) + } + + fn visit_again(&self, _path: &Vec, _b: u64) -> btree::Result<()> { + Ok(()) + } + + fn end_walk(&self) -> btree::Result<()> { + Ok(()) + } +} + +fn find_shared_nodes( + ctx: &Context, + nr_metadata_blocks: u64, + roots: &BTreeMap, +) -> Result<(BTreeSet, Arc>)> { + // By default the walker uses a restricted space map that can only count to 1. So + // we explicitly create a full sm. + let sm = core_sm(nr_metadata_blocks, roots.len() as u32); + let w = BTreeWalker::new_with_sm(ctx.engine.clone(), sm.clone(), false)?; + + let mut path = Vec::new(); + path.push(0); + + for (thin_id, root) in roots { + ctx.report.info(&format!("scanning {}", thin_id)); + let v = NoopVisitor {}; + w.walk::(&mut path, &v, *root)?; + } + + let mut shared = BTreeSet::new(); + { + let sm = sm.lock().unwrap(); + for i in 0..sm.get_nr_blocks().unwrap() { + if sm.get(i).expect("couldn't get count from space map.") > 1 { + shared.insert(i); + } + } + } + + return Ok((shared, sm)); +} + +//------------------------------------------ + +fn dump_node( + ctx: &Context, + out: &mut dyn xml::MetadataVisitor, + root: u64, + sm: &Arc>, + force: bool, // sets the ref count for the root to zero to force output. +) -> Result<()> { + let w = BTreeWalker::new_with_sm(ctx.engine.clone(), sm.clone(), false)?; + let mut path = Vec::new(); + path.push(0); + + let v = MappingVisitor::new(out); + + // Temporarily set the ref count for the root to zero. + let mut old_count = 0; + if force { + let mut sm = sm.lock().unwrap(); + old_count = sm.get(root).unwrap(); + sm.set(root, 0)?; + } + + w.walk::(&mut path, &v, root)?; + + // Reset the ref count for root. + if force { + let mut sm = sm.lock().unwrap(); + sm.set(root, old_count)?; + } + + Ok(()) +} + +//------------------------------------------ + +pub fn dump(opts: ThinDumpOptions) -> Result<()> { + let ctx = mk_context(&opts)?; + + let report = &ctx.report; + let engine = &ctx.engine; + + // superblock + report.set_title("Reading superblock"); + let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?; + let metadata_root = unpack::(&sb.metadata_sm_root[0..])?; + let data_root = unpack::(&sb.data_sm_root[0..])?; + let mut path = Vec::new(); + path.push(0); + + report.set_title("Reading device details"); + let devs = btree_to_map::(&mut path, engine.clone(), true, sb.details_root)?; + + report.set_title("Reading mappings roots"); + let roots = btree_to_map::(&mut path, engine.clone(), true, sb.mapping_root)?; + + report.set_title("Finding shared mappings"); + let (shared, sm) = find_shared_nodes(&ctx, metadata_root.nr_blocks, &roots)?; + report.info(&format!("{} shared nodes found", shared.len())); + + let mut out = xml::XmlWriter::new(std::io::stdout()); + let xml_sb = xml::Superblock { + uuid: "".to_string(), + time: sb.time as u64, + transaction: sb.transaction_id, + flags: None, + version: Some(2), + data_block_size: sb.data_block_size, + nr_data_blocks: data_root.nr_blocks, + metadata_snap: None, + }; + out.superblock_b(&xml_sb)?; + + report.set_title("Dumping shared regions"); + for b in shared { + out.def_shared_b(&format!("{}", b))?; + dump_node(&ctx, &mut out, b, &sm, true)?; + out.def_shared_e()?; + } + + report.set_title("Dumping mappings"); + for (thin_id, detail) in devs { + let d = xml::Device { + dev_id: thin_id as u32, + mapped_blocks: detail.mapped_blocks, + transaction: detail.transaction_id, + creation_time: detail.creation_time as u64, + snap_time: detail.snapshotted_time as u64, + }; + out.device_b(&d)?; + let root = roots.get(&thin_id).unwrap(); + dump_node(&ctx, &mut out, *root, &sm, false)?; + out.device_e()?; + } + out.superblock_e()?; + + Ok(()) +} + +//------------------------------------------ diff --git a/src/thin/mod.rs b/src/thin/mod.rs index f5a4024..3f2653a 100644 --- a/src/thin/mod.rs +++ b/src/thin/mod.rs @@ -2,4 +2,5 @@ pub mod block_time; pub mod device_detail; pub mod superblock; pub mod check; +pub mod dump; pub mod xml; diff --git a/src/thin/xml.rs b/src/thin/xml.rs index 73155d3..752dd8e 100644 --- a/src/thin/xml.rs +++ b/src/thin/xml.rs @@ -46,10 +46,14 @@ pub trait MetadataVisitor { fn superblock_b(&mut self, sb: &Superblock) -> Result; fn superblock_e(&mut self) -> Result; + fn def_shared_b(&mut self, name: &str) -> Result; + fn def_shared_e(&mut self) -> Result; + fn device_b(&mut self, d: &Device) -> Result; fn device_e(&mut self) -> Result; fn map(&mut self, m: &Map) -> Result; + fn ref_shared(&mut self, name: &str) -> Result; fn eof(&mut self) -> Result; } @@ -110,6 +114,19 @@ impl MetadataVisitor for XmlWriter { Ok(Visit::Continue) } + fn def_shared_b(&mut self, name: &str) -> Result { + let tag = b"def"; + let mut elem = BytesStart::owned(tag.to_vec(), tag.len()); + elem.push_attribute(mk_attr(b"name", name)); + self.w.write_event(Event::Start(elem))?; + Ok(Visit::Continue) + } + + fn def_shared_e(&mut self) -> Result { + self.w.write_event(Event::End(BytesEnd::borrowed(b"def")))?; + Ok(Visit::Continue) + } + fn device_b(&mut self, d: &Device) -> Result { let tag = b"device"; let mut elem = BytesStart::owned(tag.to_vec(), tag.len()); @@ -151,6 +168,14 @@ impl MetadataVisitor for XmlWriter { Ok(Visit::Continue) } + fn ref_shared(&mut self, name: &str) -> Result { + let tag = b"ref"; + let mut elem = BytesStart::owned(tag.to_vec(), tag.len()); + elem.push_attribute(mk_attr(b"name", name)); + self.w.write_event(Event::Empty(elem))?; + Ok(Visit::Continue) + } + fn eof(&mut self) -> Result { let w = self.w.inner(); w.flush()?; @@ -379,6 +404,14 @@ impl MetadataVisitor for SBVisitor { Ok(Visit::Continue) } + fn def_shared_b(&mut self, _name: &str) -> Result { + Ok(Visit::Continue) + } + + fn def_shared_e(&mut self) -> Result { + Ok(Visit::Continue) + } + fn device_b(&mut self, _d: &Device) -> Result { Ok(Visit::Continue) } @@ -390,6 +423,10 @@ impl MetadataVisitor for SBVisitor { Ok(Visit::Continue) } + fn ref_shared(&mut self, _name: &str) -> Result { + Ok(Visit::Continue) + } + fn eof(&mut self) -> Result { Ok(Visit::Stop) } diff --git a/tests/thin_shrink.rs b/tests/thin_shrink.rs index 0856859..6bed874 100644 --- a/tests/thin_shrink.rs +++ b/tests/thin_shrink.rs @@ -103,6 +103,14 @@ impl<'a, V: ThinVisitor> xml::MetadataVisitor for ThinXmlVisitor<'a, V> { Ok(Visit::Continue) } + fn def_shared_b(&mut self, _name: &str) -> Result { + todo!(); + } + + fn def_shared_e(&mut self) -> Result { + todo!(); + } + fn device_b(&mut self, d: &xml::Device) -> Result { self.thin_id = Some(d.dev_id); Ok(Visit::Continue) @@ -125,6 +133,10 @@ impl<'a, V: ThinVisitor> xml::MetadataVisitor for ThinXmlVisitor<'a, V> { Ok(Visit::Continue) } + fn ref_shared(&mut self, _name: &str) -> Result { + todo!(); + } + fn eof(&mut self) -> Result { Ok(Visit::Stop) }