[thin_dump (rust)] First pass at thin_dump.
Doesn't include --repair. This includes <def> and <ref> sections for shared regions.
This commit is contained in:
parent
a88ae3ca18
commit
e9fbcc31de
105
src/bin/thin_dump.rs
Normal file
105
src/bin/thin_dump.rs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -550,6 +550,11 @@ pub trait NodeVisitor<V: Unpack> {
|
|||||||
values: &[V],
|
values: &[V],
|
||||||
) -> Result<()>;
|
) -> 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<u64>, b: u64) -> Result<()>;
|
||||||
|
|
||||||
fn end_walk(&self) -> Result<()>;
|
fn end_walk(&self) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,7 +659,11 @@ impl BTreeWalker {
|
|||||||
// This node has already been checked ...
|
// This node has already been checked ...
|
||||||
match self.failed(bs[i]) {
|
match self.failed(bs[i]) {
|
||||||
None => {
|
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) => {
|
Some(e) => {
|
||||||
// ... there was an error
|
// ... there was an error
|
||||||
@ -773,7 +782,7 @@ impl BTreeWalker {
|
|||||||
if let Some(e) = self.failed(root) {
|
if let Some(e) = self.failed(root) {
|
||||||
Err(e.clone())
|
Err(e.clone())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
visitor.visit_again(path, root)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let root = self.engine.read(root).map_err(|_| io_err(path))?;
|
let root = self.engine.read(root).map_err(|_| io_err(path))?;
|
||||||
@ -878,7 +887,11 @@ where
|
|||||||
// This node has already been checked ...
|
// This node has already been checked ...
|
||||||
match w.failed(bs[i]) {
|
match w.failed(bs[i]) {
|
||||||
None => {
|
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) => {
|
Some(e) => {
|
||||||
// ... there was an error
|
// ... there was an error
|
||||||
@ -953,7 +966,7 @@ where
|
|||||||
if let Some(e) = w.failed(root) {
|
if let Some(e) = w.failed(root) {
|
||||||
Err(e.clone())
|
Err(e.clone())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
visitor.visit_again(path, root)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let root = w.engine.read(root).map_err(|_| io_err(path))?;
|
let root = w.engine.read(root).map_err(|_| io_err(path))?;
|
||||||
@ -997,6 +1010,10 @@ impl<V: Unpack + Copy> NodeVisitor<V> for ValueCollector<V> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_again(&self, _path: &Vec<u64>, _b: u64) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn end_walk(&self) -> Result<()> {
|
fn end_walk(&self) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1060,6 +1077,10 @@ impl<V: Unpack + Clone> NodeVisitor<V> for ValuePathCollector<V> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_again(&self, _path: &Vec<u64>, _b: u64) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn end_walk(&self) -> Result<()> {
|
fn end_walk(&self) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -225,6 +225,10 @@ pub trait SpaceMap {
|
|||||||
fn get_nr_blocks(&self) -> Result<u64>;
|
fn get_nr_blocks(&self) -> Result<u64>;
|
||||||
fn get_nr_allocated(&self) -> Result<u64>;
|
fn get_nr_allocated(&self) -> Result<u64>;
|
||||||
fn get(&self, b: u64) -> Result<u32>;
|
fn get(&self, b: u64) -> Result<u32>;
|
||||||
|
|
||||||
|
// Returns the old ref count
|
||||||
|
fn set(&mut self, b: u64, v: u32) -> Result<u32>;
|
||||||
|
|
||||||
fn inc(&mut self, begin: u64, len: u64) -> Result<()>;
|
fn inc(&mut self, begin: u64, len: u64) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,6 +269,20 @@ where
|
|||||||
Ok(self.counts[b as usize].into())
|
Ok(self.counts[b as usize].into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, b: u64, v: u32) -> Result<u32> {
|
||||||
|
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<()> {
|
fn inc(&mut self, begin: u64, len: u64) -> Result<()> {
|
||||||
for b in begin..(begin + len) {
|
for b in begin..(begin + len) {
|
||||||
if self.counts[b as usize] == V::from(0u8) {
|
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<u32> {
|
||||||
|
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<()> {
|
fn inc(&mut self, begin: u64, len: u64) -> Result<()> {
|
||||||
for b in begin..(begin + len) {
|
for b in begin..(begin + len) {
|
||||||
if !self.counts.contains(b as usize) {
|
if !self.counts.contains(b as usize) {
|
||||||
|
@ -45,6 +45,14 @@ impl xml::MetadataVisitor for Pass1 {
|
|||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn def_shared_b(&mut self, _name: &str) -> Result<Visit> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn def_shared_e(&mut self) -> Result<Visit> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
fn device_b(&mut self, _d: &xml::Device) -> Result<Visit> {
|
fn device_b(&mut self, _d: &xml::Device) -> Result<Visit> {
|
||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
@ -63,6 +71,10 @@ impl xml::MetadataVisitor for Pass1 {
|
|||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ref_shared(&mut self, _name: &str) -> Result<Visit> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
fn eof(&mut self) -> Result<Visit> {
|
fn eof(&mut self) -> Result<Visit> {
|
||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
@ -96,6 +108,14 @@ impl<W: Write> xml::MetadataVisitor for Pass2<W> {
|
|||||||
self.writer.superblock_e()
|
self.writer.superblock_e()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn def_shared_b(&mut self, _name: &str) -> Result<Visit> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn def_shared_e(&mut self) -> Result<Visit> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
fn device_b(&mut self, d: &xml::Device) -> Result<Visit> {
|
fn device_b(&mut self, d: &xml::Device) -> Result<Visit> {
|
||||||
self.writer.device_b(d)
|
self.writer.device_b(d)
|
||||||
}
|
}
|
||||||
@ -127,6 +147,10 @@ impl<W: Write> xml::MetadataVisitor for Pass2<W> {
|
|||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ref_shared(&mut self, _name: &str) -> Result<Visit> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
fn eof(&mut self) -> Result<Visit> {
|
fn eof(&mut self) -> Result<Visit> {
|
||||||
self.writer.eof()
|
self.writer.eof()
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,10 @@ impl NodeVisitor<BlockTime> for BottomLevelVisitor {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_again(&self, _path: &Vec<u64>, _b: u64) -> btree::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn end_walk(&self) -> btree::Result<()> {
|
fn end_walk(&self) -> btree::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -98,6 +102,10 @@ impl<'a> NodeVisitor<u32> for OverflowChecker<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_again(&self, _path: &Vec<u64>, _b: u64) -> btree::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn end_walk(&self) -> btree::Result<()> {
|
fn end_walk(&self) -> btree::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
323
src/thin/dump.rs
Normal file
323
src/thin/dump.rs
Normal file
@ -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<xml::Map>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunBuilder {
|
||||||
|
fn new() -> RunBuilder {
|
||||||
|
RunBuilder { run: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self, thin_block: u64, data_block: u64, time: u32) -> Option<xml::Map> {
|
||||||
|
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<xml::Map> {
|
||||||
|
self.run.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
struct MVInner<'a> {
|
||||||
|
md_out: &'a mut dyn xml::MetadataVisitor,
|
||||||
|
builder: RunBuilder,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MappingVisitor<'a> {
|
||||||
|
inner: Mutex<MVInner<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
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<BlockTime> for MappingVisitor<'a> {
|
||||||
|
fn visit(
|
||||||
|
&self,
|
||||||
|
_path: &Vec<u64>,
|
||||||
|
_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<u64>, 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<Report>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
report: Arc<Report>,
|
||||||
|
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mk_context(opts: &ThinDumpOptions) -> Result<Context> {
|
||||||
|
let engine: Arc<dyn IoEngine + Send + Sync>;
|
||||||
|
|
||||||
|
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<V: Unpack> btree::NodeVisitor<V> for NoopVisitor {
|
||||||
|
fn visit(
|
||||||
|
&self,
|
||||||
|
_path: &Vec<u64>,
|
||||||
|
_kr: &btree::KeyRange,
|
||||||
|
_h: &btree::NodeHeader,
|
||||||
|
_k: &[u64],
|
||||||
|
_values: &[V],
|
||||||
|
) -> btree::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_again(&self, _path: &Vec<u64>, _b: u64) -> btree::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_walk(&self) -> btree::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_shared_nodes(
|
||||||
|
ctx: &Context,
|
||||||
|
nr_metadata_blocks: u64,
|
||||||
|
roots: &BTreeMap<u64, u64>,
|
||||||
|
) -> Result<(BTreeSet<u64>, Arc<Mutex<dyn SpaceMap + Send + Sync>>)> {
|
||||||
|
// 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::<NoopVisitor, BlockTime>(&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<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
|
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::<MappingVisitor, BlockTime>(&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::<SMRoot>(&sb.metadata_sm_root[0..])?;
|
||||||
|
let data_root = unpack::<SMRoot>(&sb.data_sm_root[0..])?;
|
||||||
|
let mut path = Vec::new();
|
||||||
|
path.push(0);
|
||||||
|
|
||||||
|
report.set_title("Reading device details");
|
||||||
|
let devs = btree_to_map::<DeviceDetail>(&mut path, engine.clone(), true, sb.details_root)?;
|
||||||
|
|
||||||
|
report.set_title("Reading mappings roots");
|
||||||
|
let roots = btree_to_map::<u64>(&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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
@ -2,4 +2,5 @@ pub mod block_time;
|
|||||||
pub mod device_detail;
|
pub mod device_detail;
|
||||||
pub mod superblock;
|
pub mod superblock;
|
||||||
pub mod check;
|
pub mod check;
|
||||||
|
pub mod dump;
|
||||||
pub mod xml;
|
pub mod xml;
|
||||||
|
@ -46,10 +46,14 @@ pub trait MetadataVisitor {
|
|||||||
fn superblock_b(&mut self, sb: &Superblock) -> Result<Visit>;
|
fn superblock_b(&mut self, sb: &Superblock) -> Result<Visit>;
|
||||||
fn superblock_e(&mut self) -> Result<Visit>;
|
fn superblock_e(&mut self) -> Result<Visit>;
|
||||||
|
|
||||||
|
fn def_shared_b(&mut self, name: &str) -> Result<Visit>;
|
||||||
|
fn def_shared_e(&mut self) -> Result<Visit>;
|
||||||
|
|
||||||
fn device_b(&mut self, d: &Device) -> Result<Visit>;
|
fn device_b(&mut self, d: &Device) -> Result<Visit>;
|
||||||
fn device_e(&mut self) -> Result<Visit>;
|
fn device_e(&mut self) -> Result<Visit>;
|
||||||
|
|
||||||
fn map(&mut self, m: &Map) -> Result<Visit>;
|
fn map(&mut self, m: &Map) -> Result<Visit>;
|
||||||
|
fn ref_shared(&mut self, name: &str) -> Result<Visit>;
|
||||||
|
|
||||||
fn eof(&mut self) -> Result<Visit>;
|
fn eof(&mut self) -> Result<Visit>;
|
||||||
}
|
}
|
||||||
@ -110,6 +114,19 @@ impl<W: Write> MetadataVisitor for XmlWriter<W> {
|
|||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn def_shared_b(&mut self, name: &str) -> Result<Visit> {
|
||||||
|
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<Visit> {
|
||||||
|
self.w.write_event(Event::End(BytesEnd::borrowed(b"def")))?;
|
||||||
|
Ok(Visit::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
fn device_b(&mut self, d: &Device) -> Result<Visit> {
|
fn device_b(&mut self, d: &Device) -> Result<Visit> {
|
||||||
let tag = b"device";
|
let tag = b"device";
|
||||||
let mut elem = BytesStart::owned(tag.to_vec(), tag.len());
|
let mut elem = BytesStart::owned(tag.to_vec(), tag.len());
|
||||||
@ -151,6 +168,14 @@ impl<W: Write> MetadataVisitor for XmlWriter<W> {
|
|||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ref_shared(&mut self, name: &str) -> Result<Visit> {
|
||||||
|
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<Visit> {
|
fn eof(&mut self) -> Result<Visit> {
|
||||||
let w = self.w.inner();
|
let w = self.w.inner();
|
||||||
w.flush()?;
|
w.flush()?;
|
||||||
@ -379,6 +404,14 @@ impl MetadataVisitor for SBVisitor {
|
|||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn def_shared_b(&mut self, _name: &str) -> Result<Visit> {
|
||||||
|
Ok(Visit::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn def_shared_e(&mut self) -> Result<Visit> {
|
||||||
|
Ok(Visit::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
fn device_b(&mut self, _d: &Device) -> Result<Visit> {
|
fn device_b(&mut self, _d: &Device) -> Result<Visit> {
|
||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
@ -390,6 +423,10 @@ impl MetadataVisitor for SBVisitor {
|
|||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ref_shared(&mut self, _name: &str) -> Result<Visit> {
|
||||||
|
Ok(Visit::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
fn eof(&mut self) -> Result<Visit> {
|
fn eof(&mut self) -> Result<Visit> {
|
||||||
Ok(Visit::Stop)
|
Ok(Visit::Stop)
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,14 @@ impl<'a, V: ThinVisitor> xml::MetadataVisitor for ThinXmlVisitor<'a, V> {
|
|||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn def_shared_b(&mut self, _name: &str) -> Result<Visit> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn def_shared_e(&mut self) -> Result<Visit> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
fn device_b(&mut self, d: &xml::Device) -> Result<Visit> {
|
fn device_b(&mut self, d: &xml::Device) -> Result<Visit> {
|
||||||
self.thin_id = Some(d.dev_id);
|
self.thin_id = Some(d.dev_id);
|
||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
@ -125,6 +133,10 @@ impl<'a, V: ThinVisitor> xml::MetadataVisitor for ThinXmlVisitor<'a, V> {
|
|||||||
Ok(Visit::Continue)
|
Ok(Visit::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ref_shared(&mut self, _name: &str) -> Result<Visit> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
fn eof(&mut self) -> Result<Visit> {
|
fn eof(&mut self) -> Result<Visit> {
|
||||||
Ok(Visit::Stop)
|
Ok(Visit::Stop)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user