diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b423e50 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: check-merge-conflict + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace +- repo: https://github.com/doublify/pre-commit-rust + rev: master + hooks: + - id: fmt + - id: clippy diff --git a/Cargo.lock b/Cargo.lock index 7f8f53f..e4ed583 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -810,7 +810,6 @@ dependencies = [ "thiserror", "threadpool", "tui", - "typenum", ] [[package]] @@ -864,12 +863,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "typenum" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" - [[package]] name = "unicode-segmentation" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index cc558c5..b47c1b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,6 @@ threadpool = "1.8" thiserror = "1.0" tui = "0.10" termion = "1.5" -typenum = "1.12.0" [dev-dependencies] json = "0.12" diff --git a/dbg-lib/output_formatter.h b/dbg-lib/output_formatter.h index 2cc4bc5..a21a92c 100644 --- a/dbg-lib/output_formatter.h +++ b/dbg-lib/output_formatter.h @@ -6,6 +6,7 @@ #include #include +#include #include #include diff --git a/src/bin/cache_check.rs b/src/bin/cache_check.rs index 4fed20c..4db7e9b 100644 --- a/src/bin/cache_check.rs +++ b/src/bin/cache_check.rs @@ -1,15 +1,19 @@ extern crate clap; extern crate thinp; +use atty::Stream; use clap::{App, Arg}; use std::path::Path; +use std::sync::Arc; + use thinp::cache::check::{check, CacheCheckOptions}; +use thinp::report::*; //------------------------------------------ fn main() { let parser = App::new("cache_check") - .version(thinp::version::TOOLS_VERSION) + .version(thinp::version::tools_version()) .arg( Arg::with_name("INPUT") .help("Specify the input device to check") @@ -39,11 +43,36 @@ fn main() { .help("Don't check the discard bitset") .long("skip-discards") .value_name("SKIP_DISCARDS"), + ) + .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("AUTO_REPAIR") + .help("Auto repair trivial issues.") + .long("auto-repair"), + ) + .arg( + Arg::with_name("QUIET") + .help("Suppress output messages, return only exit code.") + .short("q") + .long("quiet"), ); let matches = parser.get_matches(); let input_file = Path::new(matches.value_of("INPUT").unwrap()); + 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 = CacheCheckOptions { dev: &input_file, async_io: false, @@ -51,6 +80,9 @@ fn main() { skip_mappings: matches.is_present("SKIP_MAPPINGS"), skip_hints: matches.is_present("SKIP_HINTS"), skip_discards: matches.is_present("SKIP_DISCARDS"), + ignore_non_fatal: matches.is_present("IGNORE_NON_FATAL"), + auto_repair: matches.is_present("AUTO_REPAIR"), + report, }; if let Err(reason) = check(opts) { diff --git a/src/bin/cache_dump.rs b/src/bin/cache_dump.rs new file mode 100644 index 0000000..3f585bf --- /dev/null +++ b/src/bin/cache_dump.rs @@ -0,0 +1,41 @@ +extern crate clap; +extern crate thinp; + +use clap::{App, Arg}; +use std::path::Path; +use thinp::cache::dump::{dump, CacheDumpOptions}; + +//------------------------------------------ + +fn main() { + let parser = App::new("cache_dump") + .version(thinp::version::tools_version()) + .arg( + Arg::with_name("INPUT") + .help("Specify the input device to check") + .required(true) + .index(1), + ) + .arg( + Arg::with_name("REPAIR") + .help("") + .long("repair") + .value_name("REPAIR"), + ); + + let matches = parser.get_matches(); + let input_file = Path::new(matches.value_of("INPUT").unwrap()); + + let opts = CacheDumpOptions { + dev: &input_file, + async_io: false, + repair: matches.is_present("REPAIR"), + }; + + if let Err(reason) = dump(opts) { + eprintln!("{}", reason); + std::process::exit(1); + } +} + +//------------------------------------------ diff --git a/src/bin/thin_check.rs b/src/bin/thin_check.rs index b607c58..1c1ea27 100644 --- a/src/bin/thin_check.rs +++ b/src/bin/thin_check.rs @@ -14,7 +14,7 @@ use thinp::thin::check::{check, ThinCheckOptions, MAX_CONCURRENT_IO}; fn main() { let parser = App::new("thin_check") - .version(thinp::version::TOOLS_VERSION) + .version(thinp::version::tools_version()) .about("Validates thin provisioning metadata on a device or file.") .arg( Arg::with_name("QUIET") @@ -106,14 +106,19 @@ fn main() { let engine: Arc; if matches.is_present("ASYNC_IO") { - engine = Arc::new(AsyncIoEngine::new(&input_file, MAX_CONCURRENT_IO, false).expect("unable to open input file")); + engine = Arc::new( + AsyncIoEngine::new(&input_file, MAX_CONCURRENT_IO, false) + .expect("unable to open input file"), + ); } else { let nr_threads = std::cmp::max(8, num_cpus::get() * 2); - engine = Arc::new(SyncIoEngine::new(&input_file, nr_threads, false).expect("unable to open input file")); + engine = Arc::new( + SyncIoEngine::new(&input_file, nr_threads, false).expect("unable to open input file"), + ); } let opts = ThinCheckOptions { - engine: engine, + engine, sb_only: matches.is_present("SB_ONLY"), skip_mappings: matches.is_present("SKIP_MAPPINGS"), ignore_non_fatal: matches.is_present("IGNORE_NON_FATAL"), diff --git a/src/bin/thin_dump.rs b/src/bin/thin_dump.rs index 3954249..3008e35 100644 --- a/src/bin/thin_dump.rs +++ b/src/bin/thin_dump.rs @@ -13,7 +13,7 @@ use thinp::thin::dump::{dump, ThinDumpOptions}; fn main() { let parser = App::new("thin_check") - .version(thinp::version::TOOLS_VERSION) + .version(thinp::version::tools_version()) .about("Validates thin provisioning metadata on a device or file.") .arg( Arg::with_name("QUIET") diff --git a/src/bin/thin_explore.rs b/src/bin/thin_explore.rs index 356cea6..aa2b54d 100644 --- a/src/bin/thin_explore.rs +++ b/src/bin/thin_explore.rs @@ -75,15 +75,13 @@ impl Events { let ignore_exit_key = ignore_exit_key.clone(); thread::spawn(move || { let stdin = io::stdin(); - for evt in stdin.keys() { - if let Ok(key) = evt { - if let Err(err) = tx.send(Event::Input(key)) { - eprintln!("{}", err); - return; - } - if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key { - return; - } + for key in stdin.keys().flatten() { + if let Err(err) = tx.send(Event::Input(key)) { + eprintln!("{}", err); + return; + } + if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key { + return; } } }) @@ -91,8 +89,8 @@ impl Events { Events { rx, - ignore_exit_key, input_handle, + ignore_exit_key, } } @@ -849,7 +847,7 @@ fn explore(path: &Path, node_path: Option>) -> Result<()> { fn main() -> Result<()> { let parser = App::new("thin_explore") - .version(thinp::version::TOOLS_VERSION) + .version(thinp::version::tools_version()) .about("A text user interface for examining thin metadata.") .arg( Arg::with_name("NODE_PATH") diff --git a/src/bin/thin_metadata_pack.rs b/src/bin/thin_metadata_pack.rs index d4b731f..e29678a 100644 --- a/src/bin/thin_metadata_pack.rs +++ b/src/bin/thin_metadata_pack.rs @@ -8,7 +8,7 @@ use thinp::file_utils; fn main() { let parser = App::new("thin_metadata_pack") - .version(thinp::version::TOOLS_VERSION) + .version(thinp::version::tools_version()) .about("Produces a compressed file of thin metadata. Only packs metadata blocks that are actually used.") .arg(Arg::with_name("INPUT") .help("Specify thinp metadata binary device/file") diff --git a/src/bin/thin_metadata_unpack.rs b/src/bin/thin_metadata_unpack.rs index ca6403f..0664557 100644 --- a/src/bin/thin_metadata_unpack.rs +++ b/src/bin/thin_metadata_unpack.rs @@ -10,7 +10,7 @@ use std::process::exit; fn main() { let parser = App::new("thin_metadata_unpack") - .version(thinp::version::TOOLS_VERSION) + .version(thinp::version::tools_version()) .about("Unpack a compressed file of thin metadata.") .arg( Arg::with_name("INPUT") diff --git a/src/bin/thin_restore.rs b/src/bin/thin_restore.rs index 93f62e9..243f3a4 100644 --- a/src/bin/thin_restore.rs +++ b/src/bin/thin_restore.rs @@ -13,7 +13,7 @@ use thinp::thin::restore::{restore, ThinRestoreOptions}; fn main() { let parser = App::new("thin_restore") - .version(thinp::version::TOOLS_VERSION) + .version(thinp::version::tools_version()) .about("Convert XML format metadata to binary.") .arg( Arg::with_name("OVERRIDE_MAPPING_ROOT") diff --git a/src/bin/thin_shrink.rs b/src/bin/thin_shrink.rs index b748b30..0e27f79 100644 --- a/src/bin/thin_shrink.rs +++ b/src/bin/thin_shrink.rs @@ -12,7 +12,7 @@ use thinp::file_utils; fn main() { let parser = App::new("thin_shrink") - .version(thinp::version::TOOLS_VERSION) + .version(thinp::version::tools_version()) .about("Rewrite xml metadata and move data in an inactive pool.") .arg( Arg::with_name("INPUT") diff --git a/src/cache/check.rs b/src/cache/check.rs index 265b505..26cda61 100644 --- a/src/cache/check.rs +++ b/src/cache/check.rs @@ -1,6 +1,5 @@ use anyhow::anyhow; -use std::collections::*; -use std::marker::PhantomData; +use std::collections::BTreeSet; use std::path::Path; use std::sync::{Arc, Mutex}; @@ -8,7 +7,14 @@ use crate::cache::hint::*; use crate::cache::mapping::*; use crate::cache::superblock::*; use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine}; +use crate::pdata::array::{self, ArrayBlock, ArrayError}; use crate::pdata::array_walker::*; +use crate::pdata::bitset::*; +use crate::pdata::space_map::*; +use crate::pdata::space_map_checker::*; +use crate::pdata::space_map_disk::*; +use crate::pdata::unpack::unpack; +use crate::report::*; //------------------------------------------ @@ -16,76 +22,199 @@ const MAX_CONCURRENT_IO: u32 = 1024; //------------------------------------------ -struct CheckMappingVisitor { - allowed_flags: u32, - seen_oblocks: Arc>>, +fn inc_superblock(sm: &ASpaceMap) -> anyhow::Result<()> { + let mut sm = sm.lock().unwrap(); + sm.inc(SUPERBLOCK_LOCATION, 1)?; + Ok(()) } -impl CheckMappingVisitor { - fn new(metadata_version: u32) -> CheckMappingVisitor { - let mut flags: u32 = MappingFlags::Valid as u32; - if metadata_version == 1 { - flags |= MappingFlags::Dirty as u32; +//------------------------------------------ + +mod format1 { + use super::*; + + pub struct MappingChecker { + nr_origin_blocks: u64, + seen_oblocks: Mutex>, + } + + impl MappingChecker { + pub fn new(nr_origin_blocks: Option) -> MappingChecker { + MappingChecker { + nr_origin_blocks: if let Some(n) = nr_origin_blocks { + n + } else { + MAX_ORIGIN_BLOCKS + }, + seen_oblocks: Mutex::new(BTreeSet::new()), + } } - CheckMappingVisitor { - allowed_flags: flags, - seen_oblocks: Arc::new(Mutex::new(BTreeSet::new())), + + fn check_flags(&self, m: &Mapping) -> array::Result<()> { + if (m.flags & !(MappingFlags::Valid as u32 | MappingFlags::Dirty as u32)) != 0 { + return Err(array::value_err(format!( + "unknown flags in mapping: {}", + m.flags + ))); + } + if !m.is_valid() && m.is_dirty() { + return Err(array::value_err( + "dirty bit found on an unmapped block".to_string(), + )); + } + Ok(()) + } + + fn check_oblock(&self, m: &Mapping) -> array::Result<()> { + if !m.is_valid() { + if m.oblock > 0 { + return Err(array::value_err("invalid block is mapped".to_string())); + } + return Ok(()); + } + if m.oblock >= self.nr_origin_blocks { + return Err(array::value_err( + "mapping beyond end of the origin device".to_string(), + )); + } + let mut seen_oblocks = self.seen_oblocks.lock().unwrap(); + if seen_oblocks.contains(&m.oblock) { + return Err(array::value_err("origin block already mapped".to_string())); + } + seen_oblocks.insert(m.oblock); + Ok(()) } } - fn seen_oblock(&self, b: u64) -> bool { - let seen_oblocks = self.seen_oblocks.lock().unwrap(); - return seen_oblocks.contains(&b); - } + impl ArrayVisitor for MappingChecker { + fn visit(&self, _index: u64, b: ArrayBlock) -> array::Result<()> { + let mut errs: Vec = Vec::new(); - fn record_oblock(&self, b: u64) { - let mut seen_oblocks = self.seen_oblocks.lock().unwrap(); - seen_oblocks.insert(b); - } + for i in 0..b.header.nr_entries as usize { + let m = b.values[i]; - // FIXME: is it possible to validate flags at an early phase? - // e.g., move to ctor of Mapping? - fn has_unknown_flags(&self, m: &Mapping) -> bool { - return (m.flags & self.allowed_flags) != 0; + if let Err(e) = self.check_flags(&m) { + errs.push(e); + } + if let Err(e) = self.check_oblock(&m) { + errs.push(e); + } + } + + // FIXME: duplicate to BTreeWalker::build_aggregrate() + match errs.len() { + 0 => Ok(()), + 1 => Err(errs[0].clone()), + _ => Err(array::aggregate_error(errs)), + } + } } } -impl ArrayBlockVisitor for CheckMappingVisitor { - fn visit(&self, _index: u64, m: Mapping) -> anyhow::Result<()> { - if !m.is_valid() { - return Ok(()); +mod format2 { + use super::*; + + pub struct MappingChecker { + nr_origin_blocks: u64, + inner: Mutex, + } + + struct Inner { + seen_oblocks: BTreeSet, + dirty_bits: CheckedBitSet, + } + + impl MappingChecker { + pub fn new(nr_origin_blocks: Option, dirty_bits: CheckedBitSet) -> MappingChecker { + MappingChecker { + nr_origin_blocks: if let Some(n) = nr_origin_blocks { + n + } else { + MAX_ORIGIN_BLOCKS + }, + inner: Mutex::new(Inner { + seen_oblocks: BTreeSet::new(), + dirty_bits, + }), + } } - if self.seen_oblock(m.oblock) { - return Err(anyhow!("origin block already mapped")); + fn check_flags(&self, m: &Mapping, dirty_bit: Option) -> array::Result<()> { + if (m.flags & !(MappingFlags::Valid as u32)) != 0 { + return Err(array::value_err(format!( + "unknown flags in mapping: {}", + m.flags + ))); + } + if !m.is_valid() && dirty_bit.is_some() && dirty_bit.unwrap() { + return Err(array::value_err( + "dirty bit found on an unmapped block".to_string(), + )); + } + Ok(()) } - self.record_oblock(m.oblock); + fn check_oblock(&self, m: &Mapping, seen_oblocks: &mut BTreeSet) -> array::Result<()> { + if !m.is_valid() { + if m.oblock > 0 { + return Err(array::value_err("invalid mapped block".to_string())); + } + return Ok(()); + } + if m.oblock >= self.nr_origin_blocks { + return Err(array::value_err( + "mapping beyond end of the origin device".to_string(), + )); + } + if seen_oblocks.contains(&m.oblock) { + return Err(array::value_err("origin block already mapped".to_string())); + } + seen_oblocks.insert(m.oblock); - if !self.has_unknown_flags(&m) { - return Err(anyhow!("unknown flags in mapping")); + Ok(()) } + } - Ok(()) + impl ArrayVisitor for MappingChecker { + fn visit(&self, index: u64, b: ArrayBlock) -> array::Result<()> { + let mut inner = self.inner.lock().unwrap(); + let mut errs: Vec = Vec::new(); + + let begin = index as usize * b.header.max_entries as usize; + for i in 0..b.header.nr_entries { + let m = b.values[i as usize]; + + if let Err(e) = self.check_flags(&m, inner.dirty_bits.contains(begin + i as usize)) + { + errs.push(e); + } + if let Err(e) = self.check_oblock(&m, &mut inner.seen_oblocks) { + errs.push(e); + } + } + + // FIXME: duplicate to BTreeWalker::build_aggregrate() + match errs.len() { + 0 => Ok(()), + 1 => Err(errs[0].clone()), + _ => Err(array::aggregate_error(errs)), + } + } } } //------------------------------------------ -struct CheckHintVisitor { - _not_used: PhantomData, -} +struct HintChecker; -impl CheckHintVisitor { - fn new() -> CheckHintVisitor { - CheckHintVisitor { - _not_used: PhantomData, - } +impl HintChecker { + fn new() -> HintChecker { + HintChecker } } -impl ArrayBlockVisitor> for CheckHintVisitor { - fn visit(&self, _index: u64, _hint: Hint) -> anyhow::Result<()> { +impl ArrayVisitor for HintChecker { + fn visit(&self, _index: u64, _b: ArrayBlock) -> array::Result<()> { // TODO: check hints Ok(()) } @@ -93,7 +222,7 @@ impl ArrayBlockVisitor> for CheckHintVisit //------------------------------------------ -// TODO: ignore_non_fatal, clear_needs_check, auto_repair +// TODO: clear_needs_check, auto_repair pub struct CacheCheckOptions<'a> { pub dev: &'a Path, pub async_io: bool, @@ -101,10 +230,14 @@ pub struct CacheCheckOptions<'a> { pub skip_mappings: bool, pub skip_hints: bool, pub skip_discards: bool, + pub ignore_non_fatal: bool, + pub auto_repair: bool, + pub report: Arc, } -// TODO: thread pool, report +// TODO: thread pool struct Context { + report: Arc, engine: Arc, } @@ -118,40 +251,113 @@ fn mk_context(opts: &CacheCheckOptions) -> anyhow::Result { engine = Arc::new(SyncIoEngine::new(opts.dev, nr_threads, false)?); } - Ok(Context { engine }) + Ok(Context { + report: opts.report.clone(), + engine, + }) +} + +fn check_superblock(sb: &Superblock) -> anyhow::Result<()> { + if sb.version >= 2 && sb.dirty_root == None { + return Err(anyhow!("dirty bitset not found")); + } + Ok(()) } pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> { let ctx = mk_context(&opts)?; let engine = &ctx.engine; + let metadata_sm = core_sm(engine.get_nr_blocks(), u8::MAX as u32); + inc_superblock(&metadata_sm)?; let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?; + check_superblock(&sb)?; if opts.sb_only { return Ok(()); } + let nr_origin_blocks; + if sb.flags.clean_shutdown { + let origin_sectors = sb.discard_block_size * sb.discard_nr_blocks; + nr_origin_blocks = Some(origin_sectors / sb.data_block_size as u64); + } else { + nr_origin_blocks = None; + } + // TODO: factor out into check_mappings() if !opts.skip_mappings { - let w = ArrayWalker::new(engine.clone(), false); - let c = Box::new(CheckMappingVisitor::new(sb.version)); - w.walk(c, sb.mapping_root)?; - - if sb.version >= 2 { - // TODO: check dirty bitset + let w = + ArrayWalker::new_with_sm(engine.clone(), metadata_sm.clone(), opts.ignore_non_fatal)?; + match sb.version { + 1 => { + let mut c = format1::MappingChecker::new(nr_origin_blocks); + if let Err(e) = w.walk(&mut c, sb.mapping_root) { + ctx.report.fatal(&format!("{}", e)); + } + } + 2 => { + let (dirty_bits, err) = read_bitset_with_sm( + engine.clone(), + sb.dirty_root.unwrap(), + sb.cache_blocks as usize, + metadata_sm.clone(), + opts.ignore_non_fatal, + )?; + if err.is_some() { + ctx.report.fatal(&format!("{}", err.unwrap())); + } + let mut c = format2::MappingChecker::new(nr_origin_blocks, dirty_bits); + if let Err(e) = w.walk(&mut c, sb.mapping_root) { + ctx.report.fatal(&format!("{}", e)); + } + } + v => { + return Err(anyhow!("unsupported metadata version {}", v)); + } } } - if !opts.skip_hints && sb.hint_root != 0 { - let w = ArrayWalker::new(engine.clone(), false); - type Width = typenum::U4; // FIXME: check sb.policy_hint_size - let c = Box::new(CheckHintVisitor::::new()); - w.walk(c, sb.hint_root)?; + if !opts.skip_hints && sb.hint_root != 0 && sb.policy_hint_size != 0 { + if sb.policy_hint_size != 4 { + return Err(anyhow!("cache_check only supports policy hint size of 4")); + } + let w = + ArrayWalker::new_with_sm(engine.clone(), metadata_sm.clone(), opts.ignore_non_fatal)?; + let mut c = HintChecker::new(); + if let Err(e) = w.walk(&mut c, sb.hint_root) { + ctx.report.fatal(&format!("{}", e)); + } } - if !opts.skip_discards { - // TODO: check discard bitset + // The discard bitset might not be available if the cache has never been suspended, + // e.g., a crash of freshly created cache. + if !opts.skip_discards && sb.discard_root != 0 { + let (_discard_bits, err) = read_bitset_with_sm( + engine.clone(), + sb.discard_root, + sb.discard_nr_blocks as usize, + metadata_sm.clone(), + opts.ignore_non_fatal, + )?; + if err.is_some() { + ctx.report.fatal(&format!("{}", err.unwrap())); + } + } + + let root = unpack::(&sb.metadata_sm_root[0..])?; + let metadata_leaks = check_metadata_space_map( + engine.clone(), + ctx.report.clone(), + root, + metadata_sm.clone(), + opts.ignore_non_fatal, + )?; + + if opts.auto_repair && !metadata_leaks.is_empty() { + ctx.report.info("Repairing metadata leaks."); + repair_space_map(ctx.engine.clone(), metadata_leaks, metadata_sm.clone())?; } Ok(()) diff --git a/src/cache/dump.rs b/src/cache/dump.rs new file mode 100644 index 0000000..7a88ceb --- /dev/null +++ b/src/cache/dump.rs @@ -0,0 +1,317 @@ +use anyhow::anyhow; +use fixedbitset::FixedBitSet; +use std::path::Path; +use std::sync::{Arc, Mutex}; + +use crate::cache::hint::Hint; +use crate::cache::mapping::Mapping; +use crate::cache::superblock::*; +use crate::cache::xml::{self, MetadataVisitor}; +use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine}; +use crate::pdata::array::{self, ArrayBlock}; +use crate::pdata::array_walker::*; + +//------------------------------------------ + +const MAX_CONCURRENT_IO: u32 = 1024; + +//------------------------------------------ + +mod format1 { + use super::*; + + struct Inner<'a> { + visitor: &'a mut dyn MetadataVisitor, + valid_mappings: FixedBitSet, + } + + pub struct MappingEmitter<'a> { + inner: Mutex>, + } + + impl<'a> MappingEmitter<'a> { + pub fn new(nr_entries: usize, visitor: &'a mut dyn MetadataVisitor) -> MappingEmitter<'a> { + MappingEmitter { + inner: Mutex::new(Inner { + visitor, + valid_mappings: FixedBitSet::with_capacity(nr_entries), + }), + } + } + + pub fn get_valid(self) -> FixedBitSet { + let inner = self.inner.into_inner().unwrap(); + inner.valid_mappings + } + } + + impl<'a> ArrayVisitor for MappingEmitter<'a> { + fn visit(&self, index: u64, b: ArrayBlock) -> array::Result<()> { + for i in 0..b.header.nr_entries as usize { + let map = b.values[i]; + if !map.is_valid() { + continue; + } + + let m = xml::Map { + cblock: index as u32, + oblock: map.oblock, + dirty: map.is_dirty(), + }; + + let mut inner = self.inner.lock().unwrap(); + inner.valid_mappings.set(index as usize, true); + inner + .visitor + .mapping(&m) + .map_err(|e| array::value_err(format!("{}", e)))?; + } + + Ok(()) + } + } +} + +//------------------------------------------ + +mod format2 { + use super::*; + + //------------------- + // Dirty bitset visitor + pub struct DirtyVisitor { + nr_entries: usize, + bits: Mutex, + } + + impl DirtyVisitor { + pub fn new(nr_entries: usize) -> Self { + DirtyVisitor { + nr_entries, // number of bits + bits: Mutex::new(FixedBitSet::with_capacity(nr_entries)), + } + } + + pub fn get_bits(self) -> FixedBitSet { + self.bits.into_inner().unwrap() + } + } + + impl ArrayVisitor for DirtyVisitor { + fn visit(&self, index: u64, b: ArrayBlock) -> array::Result<()> { + let mut pos = (index as usize * (b.header.max_entries as usize)) << 6; + for i in 0..b.header.nr_entries as usize { + let bits = b.values[i]; + + for bi in 0..64u64 { + if pos >= self.nr_entries { + break; + } + + self.bits.lock().unwrap().set(pos, bits & (1 << bi) != 0); + pos += 1; + } + } + Ok(()) + } + } + + //------------------- + // Mapping visitor + + struct Inner<'a> { + visitor: &'a mut dyn MetadataVisitor, + dirty_bits: FixedBitSet, + valid_mappings: FixedBitSet, + } + + pub struct MappingEmitter<'a> { + inner: Mutex>, + } + + impl<'a> MappingEmitter<'a> { + pub fn new( + nr_entries: usize, + dirty_bits: FixedBitSet, + visitor: &'a mut dyn MetadataVisitor, + ) -> MappingEmitter<'a> { + MappingEmitter { + inner: Mutex::new(Inner { + visitor, + dirty_bits, + valid_mappings: FixedBitSet::with_capacity(nr_entries), + }), + } + } + + pub fn get_valid(self) -> FixedBitSet { + let inner = self.inner.into_inner().unwrap(); + inner.valid_mappings + } + } + + impl<'a> ArrayVisitor for MappingEmitter<'a> { + fn visit(&self, index: u64, b: ArrayBlock) -> array::Result<()> { + for i in 0..b.header.nr_entries as usize { + let map = b.values[i]; + + if !map.is_valid() { + continue; + } + + let mut inner = self.inner.lock().unwrap(); + let dirty = inner.dirty_bits.contains(index as usize); + let m = xml::Map { + cblock: index as u32, + oblock: map.oblock, + dirty, + }; + + inner.valid_mappings.set(index as usize, true); + inner + .visitor + .mapping(&m) + .map_err(|e| array::value_err(format!("{}", e)))?; + } + Ok(()) + } + } +} + +//----------------------------------------- + +struct HintEmitter<'a> { + emitter: Mutex<&'a mut dyn MetadataVisitor>, + valid_mappings: FixedBitSet, +} + +impl<'a> HintEmitter<'a> { + pub fn new(emitter: &'a mut dyn MetadataVisitor, valid_mappings: FixedBitSet) -> HintEmitter { + HintEmitter { + emitter: Mutex::new(emitter), + valid_mappings, + } + } +} + +impl<'a> ArrayVisitor for HintEmitter<'a> { + fn visit(&self, index: u64, b: ArrayBlock) -> array::Result<()> { + let mut cblock = index as u32 * b.header.max_entries; + for i in 0..b.header.nr_entries as usize { + if !self.valid_mappings.contains(cblock as usize) { + continue; + } + + let hint = b.values[i]; + let h = xml::Hint { + cblock, + data: hint.hint.to_vec(), + }; + + self.emitter + .lock() + .unwrap() + .hint(&h) + .map_err(|e| array::value_err(format!("{}", e)))?; + + cblock += 1; + } + + Ok(()) + } +} + +//------------------------------------------ + +pub struct CacheDumpOptions<'a> { + pub dev: &'a Path, + pub async_io: bool, + pub repair: bool, +} + +struct Context { + engine: Arc, +} + +fn mk_context(opts: &CacheDumpOptions) -> anyhow::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 { engine }) +} + +fn dump_metadata(ctx: &Context, sb: &Superblock, _repair: bool) -> anyhow::Result<()> { + let engine = &ctx.engine; + + let mut out = xml::XmlWriter::new(std::io::stdout()); + let xml_sb = xml::Superblock { + uuid: "".to_string(), + block_size: sb.data_block_size, + nr_cache_blocks: sb.cache_blocks, + policy: std::str::from_utf8(&sb.policy_name[..])?.to_string(), + hint_width: sb.policy_hint_size, + }; + out.superblock_b(&xml_sb)?; + + out.mappings_b()?; + let valid_mappings = match sb.version { + 1 => { + let w = ArrayWalker::new(engine.clone(), false); + let mut emitter = format1::MappingEmitter::new(sb.cache_blocks as usize, &mut out); + w.walk(&mut emitter, sb.mapping_root)?; + emitter.get_valid() + } + 2 => { + // We need to walk the dirty bitset first. + let w = ArrayWalker::new(engine.clone(), false); + let mut v = format2::DirtyVisitor::new(sb.cache_blocks as usize); + + if let Some(root) = sb.dirty_root { + w.walk(&mut v, root)?; + } else { + // FIXME: is there a way this can legally happen? eg, + // a crash of a freshly created cache? + return Err(anyhow!("format 2 selected, but no dirty bitset present")); + } + let dirty_bits = v.get_bits(); + + let w = ArrayWalker::new(engine.clone(), false); + let mut emitter = + format2::MappingEmitter::new(sb.cache_blocks as usize, dirty_bits, &mut out); + w.walk(&mut emitter, sb.mapping_root)?; + emitter.get_valid() + } + v => { + return Err(anyhow!("unsupported metadata version: {}", v)); + } + }; + out.mappings_e()?; + + out.hints_b()?; + { + let w = ArrayWalker::new(engine.clone(), false); + let mut emitter = HintEmitter::new(&mut out, valid_mappings); + w.walk(&mut emitter, sb.hint_root)?; + } + out.hints_e()?; + + out.superblock_e()?; + + Ok(()) +} + +pub fn dump(opts: CacheDumpOptions) -> anyhow::Result<()> { + let ctx = mk_context(&opts)?; + let engine = &ctx.engine; + let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?; + + dump_metadata(&ctx, &sb, opts.repair) +} + +//------------------------------------------ diff --git a/src/cache/hint.rs b/src/cache/hint.rs index ea7f2d3..b066645 100644 --- a/src/cache/hint.rs +++ b/src/cache/hint.rs @@ -1,30 +1,26 @@ use nom::IResult; use std::convert::TryInto; -use std::marker::PhantomData; use crate::pdata::unpack::*; //------------------------------------------ #[derive(Clone, Copy)] -pub struct Hint { - pub hint: [u8; 4], // FIXME: support various hint sizes - _not_used: PhantomData, +pub struct Hint { + pub hint: [u8; 4], } -impl Unpack for Hint { +impl Unpack for Hint { fn disk_size() -> u32 { - Width::to_u32() + 4 } - // FIXME: support different width - fn unpack(i: &[u8]) -> IResult<&[u8], Hint> { - let size = Width::to_usize(); + fn unpack(i: &[u8]) -> IResult<&[u8], Hint> { + let size = 4; Ok(( &i[size..], Hint { hint: i[0..size].try_into().unwrap(), - _not_used: PhantomData, }, )) } diff --git a/src/cache/mapping.rs b/src/cache/mapping.rs index 4370937..ac5a036 100644 --- a/src/cache/mapping.rs +++ b/src/cache/mapping.rs @@ -5,7 +5,8 @@ use crate::pdata::unpack::*; //------------------------------------------ -static FLAGS_MASK: u64 = (1 << 16) - 1; +pub const MAX_ORIGIN_BLOCKS: u64 = 1 << 48; +const FLAGS_MASK: u64 = (1 << 16) - 1; //------------------------------------------ @@ -22,7 +23,11 @@ pub struct Mapping { impl Mapping { pub fn is_valid(&self) -> bool { - return (self.flags & MappingFlags::Valid as u32) != 0; + (self.flags & MappingFlags::Valid as u32) != 0 + } + + pub fn is_dirty(&self) -> bool { + (self.flags & MappingFlags::Dirty as u32) != 0 } } diff --git a/src/cache/mod.rs b/src/cache/mod.rs index a120ce6..5aa61fa 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -1,4 +1,5 @@ pub mod check; +pub mod dump; pub mod hint; pub mod mapping; pub mod superblock; diff --git a/src/cache/superblock.rs b/src/cache/superblock.rs index e98a3e9..c944439 100644 --- a/src/cache/superblock.rs +++ b/src/cache/superblock.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; +use nom::{bytes::complete::*, number::complete::*, IResult}; use crate::io_engine::*; -use nom::{bytes::complete::*, number::complete::*, IResult}; //------------------------------------------ @@ -14,6 +14,7 @@ const SPACE_MAP_ROOT_SIZE: usize = 128; #[derive(Debug, Clone)] pub struct SuperblockFlags { + pub clean_shutdown: bool, pub needs_check: bool, } @@ -98,7 +99,8 @@ fn unpack(data: &[u8]) -> IResult<&[u8], Superblock> { i, Superblock { flags: SuperblockFlags { - needs_check: (flags | 0x1) != 0, + clean_shutdown: (flags & 0x1) != 0, + needs_check: (flags & 0x2) != 0, }, block, version, diff --git a/src/cache/xml.rs b/src/cache/xml.rs index e811b67..8b38f03 100644 --- a/src/cache/xml.rs +++ b/src/cache/xml.rs @@ -11,22 +11,22 @@ use quick_xml::Writer; #[derive(Clone)] pub struct Superblock { pub uuid: String, - pub block_size: u64, - pub nr_cache_blocks: u64, + pub block_size: u32, + pub nr_cache_blocks: u32, pub policy: String, pub hint_width: u32, } #[derive(Clone)] pub struct Map { - pub cblock: u64, + pub cblock: u32, pub oblock: u64, pub dirty: bool, } #[derive(Clone)] pub struct Hint { - pub cblock: u64, + pub cblock: u32, pub data: Vec, } diff --git a/src/checksum.rs b/src/checksum.rs index 675e6da..01d8063 100644 --- a/src/checksum.rs +++ b/src/checksum.rs @@ -10,17 +10,20 @@ const SUPERBLOCK_CSUM_XOR: u32 = 160774; const BITMAP_CSUM_XOR: u32 = 240779; const INDEX_CSUM_XOR: u32 = 160478; const BTREE_CSUM_XOR: u32 = 121107; +const ARRAY_CSUM_XOR: u32 = 595846735; fn checksum(buf: &[u8]) -> u32 { crc32c(&buf[4..]) ^ 0xffffffff } #[derive(Debug, PartialEq)] +#[allow(clippy::upper_case_acronyms)] pub enum BT { SUPERBLOCK, NODE, INDEX, BITMAP, + ARRAY, UNKNOWN, } @@ -40,6 +43,7 @@ pub fn metadata_block_type(buf: &[u8]) -> BT { BTREE_CSUM_XOR => BT::NODE, BITMAP_CSUM_XOR => BT::BITMAP, INDEX_CSUM_XOR => BT::INDEX, + ARRAY_CSUM_XOR => BT::ARRAY, _ => BT::UNKNOWN, } } @@ -55,6 +59,7 @@ pub fn write_checksum(buf: &mut [u8], kind: BT) -> Result<()> { NODE => BTREE_CSUM_XOR, BITMAP => BITMAP_CSUM_XOR, INDEX => INDEX_CSUM_XOR, + ARRAY => ARRAY_CSUM_XOR, UNKNOWN => { return Err(anyhow!("Invalid block type")); } diff --git a/src/pack/node_encode.rs b/src/pack/node_encode.rs index 466fd40..d146ca3 100644 --- a/src/pack/node_encode.rs +++ b/src/pack/node_encode.rs @@ -114,4 +114,8 @@ pub fn pack_index(w: &mut W, bytes: &[u8]) -> PResult<()> { io_to_pr(pack_literal(w, bytes)) } +pub fn pack_array(w: &mut W, bytes: &[u8]) -> PResult<()> { + io_to_pr(pack_literal(w, bytes)) +} + //------------------------------------- diff --git a/src/pack/toplevel.rs b/src/pack/toplevel.rs index 449de91..98b4dcf 100644 --- a/src/pack/toplevel.rs +++ b/src/pack/toplevel.rs @@ -209,6 +209,7 @@ fn pack_block(w: &mut W, kind: BT, buf: &[u8]) -> Result<()> { BT::NODE => pack_btree_node(w, buf).context("unable to pack btree node")?, BT::INDEX => pack_index(w, buf).context("unable to pack space map index")?, BT::BITMAP => pack_bitmap(w, buf).context("unable to pack space map bitmap")?, + BT::ARRAY => pack_array(w, buf).context("unable to pack array block")?, BT::UNKNOWN => return Err(anyhow!("asked to pack an unknown block type")), } diff --git a/src/pdata/array.rs b/src/pdata/array.rs index b9e109d..f459d1f 100644 --- a/src/pdata/array.rs +++ b/src/pdata/array.rs @@ -1,34 +1,178 @@ -use nom::{number::complete::*, IResult}; +use nom::{multi::count, number::complete::*, IResult}; use std::fmt; +use thiserror::Error; -use crate::pdata::unpack::*; +use crate::checksum; +use crate::io_engine::BLOCK_SIZE; +use crate::pdata::btree; +use crate::pdata::unpack::Unpack; //------------------------------------------ -// TODO: build a data structure representating an array? +const ARRAY_BLOCK_HEADER_SIZE: u32 = 24; -// FIXME: rename this struct -pub struct ArrayBlockEntry { - pub block: u64, +pub struct ArrayBlockHeader { + pub csum: u32, + pub max_entries: u32, + pub nr_entries: u32, + pub value_size: u32, + pub blocknr: u64, } -impl Unpack for ArrayBlockEntry { +impl Unpack for ArrayBlockHeader { fn disk_size() -> u32 { - 8 + ARRAY_BLOCK_HEADER_SIZE } - fn unpack(i: &[u8]) -> IResult<&[u8], ArrayBlockEntry> { - let (i, n) = le_u64(i)?; - let block = n; + fn unpack(data: &[u8]) -> IResult<&[u8], ArrayBlockHeader> { + let (i, csum) = le_u32(data)?; + let (i, max_entries) = le_u32(i)?; + let (i, nr_entries) = le_u32(i)?; + let (i, value_size) = le_u32(i)?; + let (i, blocknr) = le_u64(i)?; - Ok((i, ArrayBlockEntry { block })) + Ok(( + i, + ArrayBlockHeader { + csum, + max_entries, + nr_entries, + value_size, + blocknr, + }, + )) } } -impl fmt::Display for ArrayBlockEntry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.block) - } +pub struct ArrayBlock { + pub header: ArrayBlockHeader, + pub values: Vec, +} + +//------------------------------------------ + +#[derive(Error, Clone, Debug)] +pub enum ArrayError { + //#[error("io_error {0}")] + IoError(u64), + + //#[error("block error: {0}")] + BlockError(String), + + //#[error("value error: {0}")] + ValueError(String), + + //#[error("index: {0:?}")] + IndexContext(u64, Box), + + //#[error("aggregate: {0:?}")] + Aggregate(Vec), + + //#[error("{0:?}, {1}")] + Path(Vec, Box), + + #[error(transparent)] + BTreeError(#[from] btree::BTreeError), +} + +impl fmt::Display for ArrayError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ArrayError::IoError(b) => write!(f, "io error {}", b), + ArrayError::BlockError(msg) => write!(f, "block error: {}", msg), + ArrayError::ValueError(msg) => write!(f, "value error: {}", msg), + ArrayError::IndexContext(idx, e) => { + write!(f, "{}, effecting index {}", e, idx)?; + Ok(()) + } + ArrayError::Aggregate(errs) => { + for e in errs { + write!(f, "{}", e)? + } + Ok(()) + } + ArrayError::Path(path, e) => write!(f, "{} {}", e, btree::encode_node_path(path)), + ArrayError::BTreeError(e) => write!(f, "{}", e), + } + } +} + +pub fn io_err(path: &[u64], blocknr: u64) -> ArrayError { + ArrayError::Path(path.to_vec(), Box::new(ArrayError::IoError(blocknr))) +} + +pub fn array_block_err(path: &[u64], msg: &str) -> ArrayError { + ArrayError::Path( + path.to_vec(), + Box::new(ArrayError::BlockError(msg.to_string())), + ) +} + +pub fn value_err(msg: String) -> ArrayError { + ArrayError::ValueError(msg) +} + +pub fn aggregate_error(errs: Vec) -> ArrayError { + ArrayError::Aggregate(errs) +} + +impl ArrayError { + pub fn index_context(self, index: u64) -> ArrayError { + ArrayError::IndexContext(index, Box::new(self)) + } +} + +pub type Result = std::result::Result; + +//------------------------------------------ + +fn convert_result<'a, V>(path: &[u64], r: IResult<&'a [u8], V>) -> Result<(&'a [u8], V)> { + r.map_err(|_| array_block_err(path, "parse error")) +} + +pub fn unpack_array_block(path: &[u64], data: &[u8]) -> Result> { + let bt = checksum::metadata_block_type(data); + if bt != checksum::BT::ARRAY { + return Err(array_block_err( + path, + &format!( + "checksum failed for array block {}, {:?}", + path.last().unwrap(), + bt + ), + )); + } + + let (i, header) = ArrayBlockHeader::unpack(data) + .map_err(|_| array_block_err(path, "Couldn't parse array block header"))?; + + // check value_size + if header.value_size != V::disk_size() { + return Err(array_block_err( + path, + &format!( + "value_size mismatch: expected {}, was {}", + V::disk_size(), + header.value_size + ), + )); + } + + // check max_entries + if header.value_size * header.max_entries + ARRAY_BLOCK_HEADER_SIZE > BLOCK_SIZE as u32 { + return Err(array_block_err( + path, + &format!("max_entries is too large ({})", header.max_entries), + )); + } + + // TODO: check nr_entries < max_entries + + // TODO: check block_nr + + let (_i, values) = convert_result(path, count(V::unpack, header.nr_entries as usize)(i))?; + + Ok(ArrayBlock { header, values }) } //------------------------------------------ diff --git a/src/pdata/array_block.rs b/src/pdata/array_block.rs deleted file mode 100644 index 50da5dd..0000000 --- a/src/pdata/array_block.rs +++ /dev/null @@ -1,66 +0,0 @@ -use anyhow::anyhow; -use anyhow::Result; -use nom::{multi::count, number::complete::*, IResult}; - -use crate::pdata::unpack::Unpack; - -//------------------------------------------ - -pub struct ArrayBlockHeader { - pub csum: u32, - pub max_entries: u32, - pub nr_entries: u32, - pub value_size: u32, - pub blocknr: u64, -} - -impl Unpack for ArrayBlockHeader { - fn disk_size() -> u32 { - 24 - } - - fn unpack(data: &[u8]) -> IResult<&[u8], ArrayBlockHeader> { - let (i, csum) = le_u32(data)?; - let (i, max_entries) = le_u32(i)?; - let (i, nr_entries) = le_u32(i)?; - let (i, value_size) = le_u32(i)?; - let (i, blocknr) = le_u64(i)?; - - Ok(( - i, - ArrayBlockHeader { - csum, - max_entries, - nr_entries, - value_size, - blocknr, - }, - )) - } -} - -pub struct ArrayBlock { - pub header: ArrayBlockHeader, - pub values: Vec, -} - -impl ArrayBlock { - pub fn get_header(&self) -> &ArrayBlockHeader { - &self.header - } -} - -fn convert_result<'a, V>(r: IResult<&'a [u8], V>) -> Result<(&'a [u8], V)> { - r.map_err(|_| anyhow!("parse error")) -} - -pub fn unpack_array_block(data: &[u8]) -> Result> { - // TODO: collect errors - let (i, header) = - ArrayBlockHeader::unpack(data).map_err(|_e| anyhow!("Couldn't parse header"))?; - let (_i, values) = convert_result(count(V::unpack, header.nr_entries as usize)(i))?; - - Ok(ArrayBlock { header, values }) -} - -//------------------------------------------ diff --git a/src/pdata/array_walker.rs b/src/pdata/array_walker.rs index 6aabc82..de5f43d 100644 --- a/src/pdata/array_walker.rs +++ b/src/pdata/array_walker.rs @@ -1,104 +1,173 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use crate::io_engine::*; -use crate::pdata::array::*; -use crate::pdata::array_block::*; -use crate::pdata::btree::*; +use crate::pdata::array::{self, *}; +use crate::pdata::btree::{self, *}; use crate::pdata::btree_walker::*; +use crate::pdata::space_map::*; use crate::pdata::unpack::*; //------------------------------------------ pub struct ArrayWalker { engine: Arc, + sm: Arc>, ignore_non_fatal: bool, } -// FIXME: define another Result type for array visiting? -pub trait ArrayBlockVisitor { - fn visit(&self, index: u64, v: V) -> anyhow::Result<()>; +pub trait ArrayVisitor { + fn visit(&self, index: u64, b: ArrayBlock) -> array::Result<()>; } -struct BlockValueVisitor { +//------------------------------------------ + +// FIXME: Eliminate this structure by impl NodeVisitor for ArrayWalker? +struct BlockValueVisitor<'a, V> { engine: Arc, - array_block_visitor: Box>, + array_visitor: &'a mut dyn ArrayVisitor, + sm: Arc>, + array_errs: Mutex>, } -impl BlockValueVisitor { - pub fn new>( +impl<'a, V: Unpack + Copy> BlockValueVisitor<'a, V> { + pub fn new( e: Arc, - v: Box, - ) -> BlockValueVisitor { + sm: Arc>, + v: &'a mut dyn ArrayVisitor, + ) -> BlockValueVisitor<'a, V> { BlockValueVisitor { engine: e, - array_block_visitor: v, - } - } - - pub fn visit_array_block(&self, index: u64, array_block: ArrayBlock) { - let begin = index * u64::from(array_block.header.nr_entries); - for i in 0..array_block.header.nr_entries { - self.array_block_visitor - .visit(begin + u64::from(i), array_block.values[i as usize]) - .unwrap(); + array_visitor: v, + sm, + array_errs: Mutex::new(Vec::new()), } } } -impl NodeVisitor for BlockValueVisitor { - // FIXME: return errors +impl<'a, V: Unpack + Copy> NodeVisitor for BlockValueVisitor<'a, V> { fn visit( &self, path: &[u64], - _kr: &KeyRange, + kr: &KeyRange, _h: &NodeHeader, keys: &[u64], - values: &[ArrayBlockEntry], - ) -> Result<()> { - for n in 0..keys.len() { - let index = keys[n]; - let b = self - .engine - .read(values[n].block) - .map_err(|_| io_err(path))?; - let array_block = unpack_array_block::(b.get_data()).map_err(|_| io_err(path))?; - self.visit_array_block(index, array_block); + values: &[u64], + ) -> btree::Result<()> { + let mut path = path.to_vec(); + + // The ordering of array indices had been verified in unpack_node(), + // thus checking the upper bound implies key continuity among siblings. + if *keys.first().unwrap() + keys.len() as u64 != *keys.last().unwrap() + 1 { + return Err(btree::value_err("gaps in array indicies".to_string())); } + if let Some(end) = kr.end { + if *keys.last().unwrap() + 1 != end { + return Err(btree::value_err( + "gaps or overlaps in array indicies".to_string(), + )); + } + } + + // FIXME: will the returned blocks be reordered? + match self.engine.read_many(values) { + Err(_) => { + // IO completely failed on all the child blocks + for (i, b) in values.iter().enumerate() { + // TODO: report indices of array entries based on the type size + let mut array_errs = self.array_errs.lock().unwrap(); + array_errs.push(array::io_err(&path, *b).index_context(keys[i])); + } + } + Ok(rblocks) => { + for (i, rb) in rblocks.into_iter().enumerate() { + match rb { + Err(_) => { + let mut array_errs = self.array_errs.lock().unwrap(); + array_errs.push(array::io_err(&path, values[i]).index_context(keys[i])); + } + Ok(b) => { + path.push(b.loc); + match unpack_array_block::(&path, b.get_data()) { + Ok(array_block) => { + if let Err(e) = self.array_visitor.visit(keys[i], array_block) { + self.array_errs.lock().unwrap().push(e); + } + let mut sm = self.sm.lock().unwrap(); + sm.inc(b.loc, 1).unwrap(); + } + Err(e) => { + self.array_errs.lock().unwrap().push(e); + } + } + path.pop(); + } + } + } + } + } + Ok(()) } - // FIXME: stub - fn visit_again(&self, _path: &[u64], _b: u64) -> Result<()> { + fn visit_again(&self, _path: &[u64], _b: u64) -> btree::Result<()> { Ok(()) } - // FIXME: stub - fn end_walk(&self) -> Result<()> { + fn end_walk(&self) -> btree::Result<()> { Ok(()) } } +//------------------------------------------ + impl ArrayWalker { pub fn new(engine: Arc, ignore_non_fatal: bool) -> ArrayWalker { + let nr_blocks = engine.get_nr_blocks() as u64; let r: ArrayWalker = ArrayWalker { engine, + sm: Arc::new(Mutex::new(RestrictedSpaceMap::new(nr_blocks))), ignore_non_fatal, }; r } - // FIXME: pass the visitor by reference? - // FIXME: redefine the Result type for array visiting? - pub fn walk(&self, visitor: Box, root: u64) -> Result<()> + pub fn new_with_sm( + engine: Arc, + sm: Arc>, + ignore_non_fatal: bool, + ) -> array::Result { + { + let sm = sm.lock().unwrap(); + assert_eq!(sm.get_nr_blocks().unwrap(), engine.get_nr_blocks()); + } + + Ok(ArrayWalker { + engine, + sm, + ignore_non_fatal, + }) + } + + pub fn walk(&self, visitor: &mut dyn ArrayVisitor, root: u64) -> array::Result<()> where - BV: 'static + ArrayBlockVisitor, - V: Unpack, + V: Unpack + Copy, { - let w = BTreeWalker::new(self.engine.clone(), self.ignore_non_fatal); - let mut path = Vec::new(); // FIXME: eliminate this line? - path.push(0); - let v = BlockValueVisitor::::new(self.engine.clone(), visitor); - w.walk(&mut path, &v, root) + let w = + BTreeWalker::new_with_sm(self.engine.clone(), self.sm.clone(), self.ignore_non_fatal)?; + let mut path = vec![0]; + let v = BlockValueVisitor::::new(self.engine.clone(), self.sm.clone(), visitor); + let btree_err = w.walk(&mut path, &v, root).map_err(ArrayError::BTreeError); + + let mut array_errs = v.array_errs.into_inner().unwrap(); + if let Err(e) = btree_err { + array_errs.push(e); + } + + match array_errs.len() { + 0 => Ok(()), + 1 => Err(array_errs[0].clone()), + _ => Err(ArrayError::Aggregate(array_errs)), + } } } diff --git a/src/pdata/bitset.rs b/src/pdata/bitset.rs new file mode 100644 index 0000000..2586e74 --- /dev/null +++ b/src/pdata/bitset.rs @@ -0,0 +1,109 @@ +use fixedbitset::FixedBitSet; +use std::sync::{Arc, Mutex}; + +use crate::io_engine::IoEngine; +use crate::pdata::array::{self, ArrayBlock}; +use crate::pdata::array_walker::{ArrayVisitor, ArrayWalker}; +use crate::pdata::space_map::*; + +pub struct CheckedBitSet { + bits: FixedBitSet, +} + +impl CheckedBitSet { + pub fn with_capacity(bits: usize) -> CheckedBitSet { + CheckedBitSet { + bits: FixedBitSet::with_capacity(bits << 1), + } + } + + pub fn set(&mut self, bit: usize, enabled: bool) { + self.bits.set(bit << 1, true); + self.bits.set((bit << 1) + 1, enabled); + } + + pub fn contains(&self, bit: usize) -> Option { + if !self.bits.contains(bit << 1) { + return None; + } + Some(self.bits.contains((bit << 1) + 1)) + } +} + +struct BitsetVisitor { + nr_bits: usize, + bits: Mutex, +} + +impl BitsetVisitor { + pub fn new(nr_bits: usize) -> Self { + BitsetVisitor { + nr_bits, + bits: Mutex::new(CheckedBitSet::with_capacity(nr_bits)), + } + } + + pub fn get_bitset(self) -> CheckedBitSet { + self.bits.into_inner().unwrap() + } +} + +impl ArrayVisitor for BitsetVisitor { + fn visit(&self, index: u64, b: ArrayBlock) -> array::Result<()> { + let mut begin = (index as usize * (b.header.max_entries as usize)) << 6; + if begin >= self.nr_bits as usize { + return Err(array::value_err(format!( + "bitset size exceeds limit: {} bits", + self.nr_bits + ))); + } + + for i in 0..b.header.nr_entries as usize { + let end: usize = std::cmp::min(begin + 64, self.nr_bits as usize); + let mut mask = 1; + let bits = b.values[i]; + + for bi in begin..end { + self.bits.lock().unwrap().set(bi, bits & mask != 0); + mask <<= 1; + } + begin += 64; + } + Ok(()) + } +} + +// TODO: multi-threaded is possible +pub fn read_bitset( + engine: Arc, + root: u64, + nr_bits: usize, + ignore_none_fatal: bool, +) -> (CheckedBitSet, Option) { + let w = ArrayWalker::new(engine, ignore_none_fatal); + let mut v = BitsetVisitor::new(nr_bits); + let err = w.walk(&mut v, root); + let e = match err { + Ok(()) => None, + Err(e) => Some(e), + }; + (v.get_bitset(), e) +} + +// TODO: multi-threaded is possible +pub fn read_bitset_with_sm( + engine: Arc, + root: u64, + nr_bits: usize, + sm: Arc>, + ignore_none_fatal: bool, +) -> array::Result<(CheckedBitSet, Option)> { + let w = ArrayWalker::new_with_sm(engine, sm, ignore_none_fatal)?; + let mut v = BitsetVisitor::new(nr_bits); + let err = w.walk(&mut v, root); + let e = match err { + Ok(()) => None, + Err(e) => Some(e), + }; + Ok((v.get_bitset(), e)) +} diff --git a/src/pdata/btree.rs b/src/pdata/btree.rs index 8f72856..14105a9 100644 --- a/src/pdata/btree.rs +++ b/src/pdata/btree.rs @@ -560,7 +560,10 @@ pub fn unpack_node( for k in &keys { if let Some(l) = last { if k <= l { - return Err(node_err(&path, &format!("keys out of order: {} <= {}", k, l))); + return Err(node_err( + &path, + &format!("keys out of order: {} <= {}", k, l), + )); } } diff --git a/src/pdata/btree_builder.rs b/src/pdata/btree_builder.rs index 93242bc..3fad55e 100644 --- a/src/pdata/btree_builder.rs +++ b/src/pdata/btree_builder.rs @@ -138,7 +138,7 @@ pub struct WriteResult { /// Write a node to a free metadata block. fn write_node_(w: &mut WriteBatcher, mut node: Node) -> Result { let keys = node.get_keys(); - let first_key = keys.first().unwrap_or(&0u64).clone(); + let first_key = *keys.first().unwrap_or(&0u64); let b = w.alloc()?; node.set_block(b.loc); @@ -285,8 +285,8 @@ impl<'a, V: Pack + Unpack + Clone> NodeBuilder { /// Any shared nodes that are used have their block incremented in /// the space map. Will only increment the ref count for values /// contained in the nodes if it unpacks them. - pub fn push_nodes(&mut self, w: &mut WriteBatcher, nodes: &Vec) -> Result<()> { - assert!(nodes.len() > 0); + pub fn push_nodes(&mut self, w: &mut WriteBatcher, nodes: &[NodeSummary]) -> Result<()> { + assert!(!nodes.is_empty()); // As a sanity check we make sure that all the shared nodes contain the // minimum nr of entries. @@ -298,7 +298,7 @@ impl<'a, V: Pack + Unpack + Clone> NodeBuilder { } // Decide if we're going to use the pre-built nodes. - if (self.values.len() > 0) && (self.values.len() < half_full) { + if !self.values.is_empty() && (self.values.len() < half_full) { // To avoid writing an under populated node we have to grab some // values from the first of the shared nodes. let (keys, values) = self.read_node(w, nodes.get(0).unwrap().block)?; @@ -336,7 +336,7 @@ impl<'a, V: Pack + Unpack + Clone> NodeBuilder { pub fn complete(mut self, w: &mut WriteBatcher) -> Result> { let half_full = self.max_entries_per_node / 2; - if (self.values.len() > 0) && (self.values.len() < half_full) && (self.nodes.len() > 0) { + if !self.values.is_empty() && (self.values.len() < half_full) && !self.nodes.is_empty() { // We don't have enough values to emit a node. So we're going to // have to rebalance with the previous node. self.unshift_node(w)?; @@ -344,7 +344,7 @@ impl<'a, V: Pack + Unpack + Clone> NodeBuilder { self.emit_all(w)?; - if self.nodes.len() == 0 { + if self.nodes.is_empty() { self.emit_empty_leaf(w)? } @@ -461,7 +461,7 @@ impl Builder { self.leaf_builder.push_value(w, k, v) } - pub fn push_leaves(&mut self, w: &mut WriteBatcher, leaves: &Vec) -> Result<()> { + pub fn push_leaves(&mut self, w: &mut WriteBatcher, leaves: &[NodeSummary]) -> Result<()> { self.leaf_builder.push_nodes(w, leaves) } diff --git a/src/pdata/btree_walker.rs b/src/pdata/btree_walker.rs index 3e9c2d3..801f1cd 100644 --- a/src/pdata/btree_walker.rs +++ b/src/pdata/btree_walker.rs @@ -69,10 +69,7 @@ impl BTreeWalker { fn failed(&self, b: u64) -> Option { let fails = self.fails.lock().unwrap(); - match fails.get(&b) { - None => None, - Some(e) => Some(e.clone()), - } + fails.get(&b).cloned() } fn set_fail(&self, b: u64, err: BTreeError) { @@ -379,13 +376,13 @@ where } } Ok(rblocks) => { - let errs = Arc::new(Mutex::new(Vec::new())); + let child_errs = Arc::new(Mutex::new(Vec::new())); for (i, rb) in rblocks.into_iter().enumerate() { match rb { Err(_) => { let e = io_err(path).keys_context(&filtered_krs[i]); - let mut errs = errs.lock().unwrap(); + let mut errs = child_errs.lock().unwrap(); errs.push(e.clone()); w.set_fail(blocks[i], e); } @@ -393,7 +390,7 @@ where let w = w.clone(); let visitor = visitor.clone(); let kr = filtered_krs[i].clone(); - let errs = errs.clone(); + let errs = child_errs.clone(); let mut path = path.clone(); pool.execute(move || { @@ -410,6 +407,8 @@ where } pool.join(); + let mut child_errs = Arc::try_unwrap(child_errs).unwrap().into_inner().unwrap(); + errs.append(&mut child_errs); } } @@ -565,3 +564,51 @@ pub fn btree_to_map_with_path( } //------------------------------------------ + +struct NoopVisitor { + dummy: std::marker::PhantomData, +} + +impl NoopVisitor { + pub fn new() -> NoopVisitor { + NoopVisitor { + dummy: std::marker::PhantomData, + } + } +} + +impl NodeVisitor for NoopVisitor { + fn visit( + &self, + _path: &[u64], + _kr: &KeyRange, + _header: &NodeHeader, + _keys: &[u64], + _values: &[V], + ) -> Result<()> { + Ok(()) + } + + //fn visit_again(&self, _path: &[u64], _b: u64) -> Result<()> { + fn visit_again(&self, _path: &[u64], _b: u64) -> Result<()> { + Ok(()) + } + + fn end_walk(&self) -> Result<()> { + Ok(()) + } +} + +pub fn count_btree_blocks( + engine: Arc, + path: &mut Vec, + root: u64, + metadata_sm: ASpaceMap, + ignore_non_fatal: bool, +) -> Result<()> { + let w = BTreeWalker::new_with_sm(engine, metadata_sm, ignore_non_fatal)?; + let v = NoopVisitor::::new(); + w.walk(path, &v, root) +} + +//------------------------------------------ diff --git a/src/pdata/mod.rs b/src/pdata/mod.rs index d277f7d..56e8f2c 100644 --- a/src/pdata/mod.rs +++ b/src/pdata/mod.rs @@ -1,11 +1,12 @@ pub mod array; -pub mod array_block; pub mod array_walker; +pub mod bitset; pub mod btree; pub mod btree_builder; pub mod btree_leaf_walker; pub mod btree_merge; pub mod btree_walker; pub mod space_map; +pub mod space_map_checker; pub mod space_map_disk; pub mod unpack; diff --git a/src/pdata/space_map_checker.rs b/src/pdata/space_map_checker.rs new file mode 100644 index 0000000..9a944a1 --- /dev/null +++ b/src/pdata/space_map_checker.rs @@ -0,0 +1,323 @@ +use anyhow::{anyhow, Result}; +use std::io::Cursor; +use std::sync::Arc; + +use crate::checksum; +use crate::io_engine::IoEngine; +use crate::pdata::btree::{self, *}; +use crate::pdata::btree_walker::*; +use crate::pdata::space_map::*; +use crate::pdata::space_map_disk::*; +use crate::pdata::unpack::*; +use crate::report::Report; + +//------------------------------------------ + +pub struct BitmapLeak { + blocknr: u64, // blocknr for the first entry in the bitmap + loc: u64, // location of the bitmap +} + +//------------------------------------------ + +struct OverflowChecker<'a> { + kind: &'a str, + sm: &'a dyn SpaceMap, +} + +impl<'a> OverflowChecker<'a> { + fn new(kind: &'a str, sm: &'a dyn SpaceMap) -> OverflowChecker<'a> { + OverflowChecker { kind, sm } + } +} + +impl<'a> NodeVisitor for OverflowChecker<'a> { + fn visit( + &self, + _path: &[u64], + _kr: &KeyRange, + _h: &NodeHeader, + keys: &[u64], + values: &[u32], + ) -> btree::Result<()> { + for n in 0..keys.len() { + let k = keys[n]; + let v = values[n]; + let expected = self.sm.get(k).unwrap(); + if expected != v { + return Err(value_err(format!( + "Bad reference count for {} block {}. Expected {}, but space map contains {}.", + self.kind, k, expected, v + ))); + } + } + + Ok(()) + } + + fn visit_again(&self, _path: &[u64], _b: u64) -> btree::Result<()> { + Ok(()) + } + + fn end_walk(&self) -> btree::Result<()> { + Ok(()) + } +} + +//------------------------------------------ + +fn inc_entries(sm: &ASpaceMap, entries: &[IndexEntry]) -> Result<()> { + let mut sm = sm.lock().unwrap(); + for ie in entries { + // FIXME: checksumming bitmaps? + sm.inc(ie.blocknr, 1)?; + } + Ok(()) +} + +// Compare the refernece counts in bitmaps against the expected values +// +// `sm` - The in-core space map of expected reference counts +fn check_low_ref_counts( + engine: Arc, + report: Arc, + kind: &str, + entries: Vec, + sm: ASpaceMap, +) -> Result> { + // gathering bitmap blocknr + let mut blocks = Vec::with_capacity(entries.len()); + for i in &entries { + blocks.push(i.blocknr); + } + + // read bitmap blocks + // FIXME: we should do this in batches + let blocks = engine.read_many(&blocks)?; + + // compare ref-counts in bitmap blocks + let mut leaks = 0; + let mut blocknr = 0; + let mut bitmap_leaks = Vec::new(); + let sm = sm.lock().unwrap(); + let nr_blocks = sm.get_nr_blocks()?; + for b in blocks.iter().take(entries.len()) { + match b { + Err(_e) => { + return Err(anyhow!("Unable to read bitmap block")); + } + Ok(b) => { + if checksum::metadata_block_type(&b.get_data()) != checksum::BT::BITMAP { + report.fatal(&format!( + "Index entry points to block ({}) that isn't a bitmap", + b.loc + )); + + // FIXME: revert the ref-count at b.loc? + } + + let bitmap = unpack::(b.get_data())?; + let first_blocknr = blocknr; + let mut contains_leak = false; + for e in bitmap.entries.iter() { + if blocknr >= nr_blocks { + break; + } + + match e { + BitmapEntry::Small(actual) => { + let expected = sm.get(blocknr)?; + if *actual == 1 && expected == 0 { + leaks += 1; + contains_leak = true; + } else if *actual != expected as u8 { + report.fatal(&format!("Bad reference count for {} block {}. Expected {}, but space map contains {}.", + kind, blocknr, expected, actual)); + } + } + BitmapEntry::Overflow => { + let expected = sm.get(blocknr)?; + if expected < 3 { + report.fatal(&format!("Bad reference count for {} block {}. Expected {}, but space map says it's >= 3.", + kind, blocknr, expected)); + } + } + } + blocknr += 1; + } + if contains_leak { + bitmap_leaks.push(BitmapLeak { + blocknr: first_blocknr, + loc: b.loc, + }); + } + } + } + } + + if leaks > 0 { + report.non_fatal(&format!("{} {} blocks have leaked.", leaks, kind)); + } + + Ok(bitmap_leaks) +} + +fn gather_disk_index_entries( + engine: Arc, + bitmap_root: u64, + metadata_sm: ASpaceMap, + ignore_non_fatal: bool, +) -> Result> { + let entries_map = btree_to_map_with_sm::( + &mut vec![0], + engine, + metadata_sm.clone(), + ignore_non_fatal, + bitmap_root, + )?; + + let entries: Vec = entries_map.values().cloned().collect(); + inc_entries(&metadata_sm, &entries[0..])?; + + Ok(entries) +} + +fn gather_metadata_index_entries( + engine: Arc, + bitmap_root: u64, + metadata_sm: ASpaceMap, +) -> Result> { + let b = engine.read(bitmap_root)?; + let entries = unpack::(b.get_data())?.indexes; + + // Filter out unused entries with block 0 + let entries: Vec = entries + .iter() + .take_while(|e| e.blocknr != 0) + .cloned() + .collect(); + + metadata_sm.lock().unwrap().inc(bitmap_root, 1)?; + inc_entries(&metadata_sm, &entries[0..])?; + + Ok(entries) +} + +//------------------------------------------ + +// This checks the space map and returns any leak blocks for auto-repair to process. +// +// `disk_sm` - The in-core space map of expected data block ref-counts +// `metadata_sm` - The in-core space for storing ref-counts of verified blocks +pub fn check_disk_space_map( + engine: Arc, + report: Arc, + root: SMRoot, + disk_sm: ASpaceMap, + metadata_sm: ASpaceMap, + ignore_non_fatal: bool, +) -> Result> { + let entries = gather_disk_index_entries( + engine.clone(), + root.bitmap_root, + metadata_sm.clone(), + ignore_non_fatal, + )?; + + // check overflow ref-counts + { + let sm = disk_sm.lock().unwrap(); + let v = OverflowChecker::new("data", &*sm); + let w = BTreeWalker::new_with_sm(engine.clone(), metadata_sm.clone(), false)?; + w.walk(&mut vec![0], &v, root.ref_count_root)?; + } + + // check low ref-counts in bitmaps + check_low_ref_counts(engine, report, "data", entries, disk_sm) +} + +// This checks the space map and returns any leak blocks for auto-repair to process. +// +// `metadata_sm`: The in-core space map of expected metadata block ref-counts +pub fn check_metadata_space_map( + engine: Arc, + report: Arc, + root: SMRoot, + metadata_sm: ASpaceMap, + ignore_non_fatal: bool, +) -> Result> { + count_btree_blocks::( + engine.clone(), + &mut vec![0], + root.ref_count_root, + metadata_sm.clone(), + false, + )?; + let entries = + gather_metadata_index_entries(engine.clone(), root.bitmap_root, metadata_sm.clone())?; + + // check overflow ref-counts + { + let sm = metadata_sm.lock().unwrap(); + let v = OverflowChecker::new("metadata", &*sm); + let w = BTreeWalker::new(engine.clone(), ignore_non_fatal); + w.walk(&mut vec![0], &v, root.ref_count_root)?; + } + + // check low ref-counts in bitmaps + check_low_ref_counts(engine, report, "metadata", entries, metadata_sm) +} + +// This assumes the only errors in the space map are leaks. Entries should just be +// those that contain leaks. +pub fn repair_space_map( + engine: Arc, + entries: Vec, + sm: ASpaceMap, +) -> Result<()> { + let sm = sm.lock().unwrap(); + + let mut blocks = Vec::with_capacity(entries.len()); + for i in &entries { + blocks.push(i.loc); + } + + // FIXME: we should do this in batches + let rblocks = engine.read_many(&blocks[0..])?; + let mut write_blocks = Vec::new(); + + for (i, rb) in rblocks.into_iter().enumerate() { + if let Ok(b) = rb { + let be = &entries[i]; + let mut blocknr = be.blocknr; + let mut bitmap = unpack::(b.get_data())?; + for e in bitmap.entries.iter_mut() { + if blocknr >= sm.get_nr_blocks()? { + break; + } + + if let BitmapEntry::Small(actual) = e { + let expected = sm.get(blocknr)?; + if *actual == 1 && expected == 0 { + *e = BitmapEntry::Small(0); + } + } + + blocknr += 1; + } + + let mut out = Cursor::new(b.get_data()); + bitmap.pack(&mut out)?; + checksum::write_checksum(b.get_data(), checksum::BT::BITMAP)?; + + write_blocks.push(b); + } else { + return Err(anyhow!("Unable to reread bitmap blocks for repair")); + } + } + + engine.write_many(&write_blocks[0..])?; + Ok(()) +} + +//------------------------------------------ diff --git a/src/pdata/space_map_disk.rs b/src/pdata/space_map_disk.rs index da2301e..2dee628 100644 --- a/src/pdata/space_map_disk.rs +++ b/src/pdata/space_map_disk.rs @@ -371,7 +371,7 @@ pub fn write_metadata_sm(w: &mut WriteBatcher, sm: &dyn SpaceMap) -> Result for BottomLevelVisitor { //------------------------------------------ -struct OverflowChecker<'a> { - data_sm: &'a dyn SpaceMap, -} - -impl<'a> OverflowChecker<'a> { - fn new(data_sm: &'a dyn SpaceMap) -> OverflowChecker<'a> { - OverflowChecker { data_sm } - } -} - -impl<'a> NodeVisitor for OverflowChecker<'a> { - fn visit( - &self, - _path: &[u64], - _kr: &KeyRange, - _h: &NodeHeader, - keys: &[u64], - values: &[u32], - ) -> btree::Result<()> { - for n in 0..keys.len() { - let k = keys[n]; - let v = values[n]; - let expected = self.data_sm.get(k).unwrap(); - if expected != v { - return Err(value_err(format!("Bad reference count for data block {}. Expected {}, but space map contains {}.", - k, expected, v))); - } - } - - Ok(()) - } - - fn visit_again(&self, _path: &[u64], _b: u64) -> btree::Result<()> { - Ok(()) - } - - fn end_walk(&self) -> btree::Result<()> { - Ok(()) - } -} - -//------------------------------------------ - -struct BitmapLeak { - blocknr: u64, // blocknr for the first entry in the bitmap - loc: u64, // location of the bitmap -} - -// This checks the space map and returns any leak blocks for auto-repair to process. -fn check_space_map( - path: &mut Vec, - ctx: &Context, - kind: &str, - entries: Vec, - metadata_sm: Option, - sm: ASpaceMap, - root: SMRoot, -) -> Result> { - let report = ctx.report.clone(); - let engine = ctx.engine.clone(); - - let sm = sm.lock().unwrap(); - - // overflow btree - { - let v = OverflowChecker::new(&*sm); - let w; - if metadata_sm.is_none() { - w = BTreeWalker::new(engine.clone(), false); - } else { - w = BTreeWalker::new_with_sm(engine.clone(), metadata_sm.unwrap().clone(), false)?; - } - w.walk(path, &v, root.ref_count_root)?; - } - - let mut blocks = Vec::with_capacity(entries.len()); - for i in &entries { - blocks.push(i.blocknr); - } - - // FIXME: we should do this in batches - let blocks = engine.read_many(&blocks)?; - - let mut leaks = 0; - let mut blocknr = 0; - let mut bitmap_leaks = Vec::new(); - for b in blocks.iter().take(entries.len()) { - match b { - Err(_e) => { - return Err(anyhow!("Unable to read bitmap block")); - } - Ok(b) => { - if checksum::metadata_block_type(&b.get_data()) != checksum::BT::BITMAP { - report.fatal(&format!( - "Index entry points to block ({}) that isn't a bitmap", - b.loc - )); - } - - let bitmap = unpack::(b.get_data())?; - let first_blocknr = blocknr; - let mut contains_leak = false; - for e in bitmap.entries.iter() { - if blocknr >= root.nr_blocks { - break; - } - - match e { - BitmapEntry::Small(actual) => { - let expected = sm.get(blocknr)?; - if *actual == 1 && expected == 0 { - leaks += 1; - contains_leak = true; - } else if *actual != expected as u8 { - report.fatal(&format!("Bad reference count for {} block {}. Expected {}, but space map contains {}.", - kind, blocknr, expected, actual)); - } - } - BitmapEntry::Overflow => { - let expected = sm.get(blocknr)?; - if expected < 3 { - report.fatal(&format!("Bad reference count for {} block {}. Expected {}, but space map says it's >= 3.", - kind, blocknr, expected)); - } - } - } - blocknr += 1; - } - if contains_leak { - bitmap_leaks.push(BitmapLeak { - blocknr: first_blocknr, - loc: b.loc, - }); - } - } - } - } - - if leaks > 0 { - report.non_fatal(&format!("{} {} blocks have leaked.", leaks, kind)); - } - - Ok(bitmap_leaks) -} - -// This assumes the only errors in the space map are leaks. Entries should just be -// those that contain leaks. -fn repair_space_map(ctx: &Context, entries: Vec, sm: ASpaceMap) -> Result<()> { - let engine = ctx.engine.clone(); - - let sm = sm.lock().unwrap(); - - let mut blocks = Vec::with_capacity(entries.len()); - for i in &entries { - blocks.push(i.loc); - } - - // FIXME: we should do this in batches - let rblocks = engine.read_many(&blocks[0..])?; - let mut write_blocks = Vec::new(); - - for (i, rb) in rblocks.into_iter().enumerate() { - if let Ok(b) = rb { - let be = &entries[i]; - let mut blocknr = be.blocknr; - let mut bitmap = unpack::(b.get_data())?; - for e in bitmap.entries.iter_mut() { - if blocknr >= sm.get_nr_blocks()? { - break; - } - - if let BitmapEntry::Small(actual) = e { - let expected = sm.get(blocknr)?; - if *actual == 1 && expected == 0 { - *e = BitmapEntry::Small(0); - } - } - - blocknr += 1; - } - - let mut out = Cursor::new(b.get_data()); - bitmap.pack(&mut out)?; - checksum::write_checksum(b.get_data(), checksum::BT::BITMAP)?; - - write_blocks.push(b); - } else { - return Err(anyhow!("Unable to reread bitmap blocks for repair")); - } - } - - engine.write_many(&write_blocks[0..])?; - Ok(()) -} - -//------------------------------------------ - -fn inc_entries(sm: &ASpaceMap, entries: &[IndexEntry]) -> Result<()> { - let mut sm = sm.lock().unwrap(); - for ie in entries { - sm.inc(ie.blocknr, 1)?; - } - Ok(()) -} - fn inc_superblock(sm: &ASpaceMap) -> Result<()> { let mut sm = sm.lock().unwrap(); sm.inc(SUPERBLOCK_LOCATION, 1)?; @@ -441,8 +235,7 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { } let metadata_root = unpack::(&sb.metadata_sm_root[0..])?; - let mut path = Vec::new(); - path.push(0); + let mut path = vec![0]; // Device details. We read this once to get the number of thin devices, and hence the // maximum metadata ref count. Then create metadata space map, and reread to increment @@ -492,30 +285,22 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { check_mapping_bottom_level(&ctx, &metadata_sm, &data_sm, &roots)?; bail_out(&ctx, "mapping tree")?; + //----------------------------------------- + report.set_sub_title("data space map"); let root = unpack::(&sb.data_sm_root[0..])?; - - let entries = btree_to_map_with_sm::( - &mut path, + let data_leaks = check_disk_space_map( engine.clone(), + report.clone(), + root, + data_sm.clone(), metadata_sm.clone(), opts.ignore_non_fatal, - root.bitmap_root, - )?; - let entries: Vec = entries.values().cloned().collect(); - inc_entries(&metadata_sm, &entries[0..])?; - - let data_leaks = check_space_map( - &mut path, - &ctx, - "data", - entries, - Some(metadata_sm.clone()), - data_sm.clone(), - root, )?; bail_out(&ctx, "data space map")?; + //----------------------------------------- + report.set_sub_title("metadata space map"); let root = unpack::(&sb.metadata_sm_root[0..])?; report.info(&format!( @@ -523,49 +308,28 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { root.nr_blocks - root.nr_allocated )); - let b = engine.read(root.bitmap_root)?; - metadata_sm.lock().unwrap().inc(root.bitmap_root, 1)?; - let entries = unpack::(b.get_data())?.indexes; - - // Unused entries will point to block 0 - let entries: Vec = entries - .iter() - .take_while(|e| e.blocknr != 0) - .cloned() - .collect(); - inc_entries(&metadata_sm, &entries[0..])?; - - // We call this for the side effect of incrementing the ref counts - // for the metadata that holds the tree. - let _counts = btree_to_map_with_sm::( - &mut path, + // Now the counts should be correct and we can check it. + let metadata_leaks = check_metadata_space_map( engine.clone(), + report.clone(), + root, metadata_sm.clone(), opts.ignore_non_fatal, - root.ref_count_root, )?; - // Now the counts should be correct and we can check it. - let metadata_leaks = check_space_map( - &mut path, - &ctx, - "metadata", - entries, - None, - metadata_sm.clone(), - root, - )?; bail_out(&ctx, "metadata space map")?; + //----------------------------------------- + if opts.auto_repair { if !data_leaks.is_empty() { ctx.report.info("Repairing data leaks."); - repair_space_map(&ctx, data_leaks, data_sm.clone())?; + repair_space_map(ctx.engine.clone(), data_leaks, data_sm.clone())?; } if !metadata_leaks.is_empty() { ctx.report.info("Repairing metadata leaks."); - repair_space_map(&ctx, metadata_leaks, metadata_sm.clone())?; + repair_space_map(ctx.engine.clone(), metadata_leaks, metadata_sm.clone())?; } } @@ -583,7 +347,10 @@ pub struct CheckMaps { pub data_sm: Arc>, } -pub fn check_with_maps(engine: Arc, report: Arc) -> Result { +pub fn check_with_maps( + engine: Arc, + report: Arc, +) -> Result { let ctx = mk_context(engine.clone(), report.clone())?; report.set_title("Checking thin metadata"); @@ -593,18 +360,12 @@ pub fn check_with_maps(engine: Arc, report: Arc(&sb.metadata_sm_root[0..])?; - let mut path = Vec::new(); - path.push(0); + let mut path = vec![0]; // Device details. We read this once to get the number of thin devices, and hence the // maximum metadata ref count. Then create metadata space map, and reread to increment // the ref counts for that metadata. - let devs = btree_to_map::( - &mut path, - engine.clone(), - false, - sb.details_root, - )?; + let devs = btree_to_map::(&mut path, engine.clone(), false, sb.details_root)?; let nr_devs = devs.len(); let metadata_sm = core_sm(engine.get_nr_blocks(), nr_devs as u32); inc_superblock(&metadata_sm)?; @@ -640,30 +401,22 @@ pub fn check_with_maps(engine: Arc, report: Arc(&sb.data_sm_root[0..])?; - - let entries = btree_to_map_with_sm::( - &mut path, + let _data_leaks = check_disk_space_map( engine.clone(), + report.clone(), + root, + data_sm.clone(), metadata_sm.clone(), false, - root.bitmap_root, - )?; - let entries: Vec = entries.values().cloned().collect(); - inc_entries(&metadata_sm, &entries[0..])?; - - let _data_leaks = check_space_map( - &mut path, - &ctx, - "data", - entries, - Some(metadata_sm.clone()), - data_sm.clone(), - root, )?; bail_out(&ctx, "data space map")?; + //----------------------------------------- + report.set_sub_title("metadata space map"); let root = unpack::(&sb.metadata_sm_root[0..])?; report.info(&format!( @@ -671,40 +424,13 @@ pub fn check_with_maps(engine: Arc, report: Arc(b.get_data())?.indexes; - - // Unused entries will point to block 0 - let entries: Vec = entries - .iter() - .take_while(|e| e.blocknr != 0) - .cloned() - .collect(); - inc_entries(&metadata_sm, &entries[0..])?; - - // We call this for the side effect of incrementing the ref counts - // for the metadata that holds the tree. - let _counts = btree_to_map_with_sm::( - &mut path, - engine.clone(), - metadata_sm.clone(), - false, - root.ref_count_root, - )?; - // Now the counts should be correct and we can check it. - let _metadata_leaks = check_space_map( - &mut path, - &ctx, - "metadata", - entries, - None, - metadata_sm.clone(), - root, - )?; + let _metadata_leaks = + check_metadata_space_map(engine.clone(), report, root, metadata_sm.clone(), false)?; bail_out(&ctx, "metadata space map")?; + //----------------------------------------- + stop_progress.store(true, Ordering::Relaxed); tid.join().unwrap(); @@ -714,4 +440,4 @@ pub fn check_with_maps(engine: Arc, report: Arc(&mut path, &mut v, *r)?; @@ -323,8 +322,7 @@ fn build_metadata(ctx: &Context, sb: &Superblock) -> Result { report.set_title("Reading superblock"); //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); + let mut path = vec![0]; report.set_title("Reading device details"); let details = btree_to_map::(&mut path, engine.clone(), true, sb.details_root)?; @@ -385,6 +383,7 @@ fn build_metadata(ctx: &Context, sb: &Superblock) -> Result { //------------------------------------------ +#[allow(dead_code)] fn gather_entries(g: &mut Gatherer, es: &[Entry]) { g.new_seq(); for e in es { @@ -399,6 +398,7 @@ fn gather_entries(g: &mut Gatherer, es: &[Entry]) { } } +#[allow(dead_code)] fn entries_to_runs(runs: &BTreeMap>, es: &[Entry]) -> Vec { use Entry::*; @@ -427,6 +427,7 @@ fn entries_to_runs(runs: &BTreeMap>, es: &[Entry]) -> Vec { // FIXME: do we really need to track kr? // FIXME: I think this may be better done as part of restore. +#[allow(dead_code)] fn optimise_metadata(md: Metadata) -> Result { use Entry::*; diff --git a/src/thin/restore.rs b/src/thin/restore.rs index e6a0a15..612dd93 100644 --- a/src/thin/restore.rs +++ b/src/thin/restore.rs @@ -89,7 +89,7 @@ impl<'a> Pass1<'a> { if let Some((name, nodes)) = current { Ok((name, nodes.complete(self.w)?)) } else { - let msg = format!("Unbalanced tag"); + let msg = "Unbalanced tag".to_string(); Err(anyhow!(msg)) } } @@ -154,7 +154,7 @@ impl<'a> MetadataVisitor for Pass1<'a> { } Ok(Visit::Continue) } else { - let msg = format!("Mapping tags must appear within a or tag."); + let msg = "Mapping tags must appear within a or tag.".to_string(); Err(anyhow!(msg)) } } diff --git a/src/thin/superblock.rs b/src/thin/superblock.rs index db7074c..f066032 100644 --- a/src/thin/superblock.rs +++ b/src/thin/superblock.rs @@ -110,14 +110,14 @@ fn pack_superblock(sb: &Superblock, w: &mut W) -> Result<()> { } w.write_u64::(sb.block)?; - w.write_all(&vec![0; UUID_SIZE])?; + w.write_all(&[0; UUID_SIZE])?; w.write_u64::(MAGIC)?; w.write_u32::(sb.version)?; w.write_u32::(sb.time)?; w.write_u64::(sb.transaction_id)?; w.write_u64::(sb.metadata_snap)?; - w.write_all(&vec![0; SPACE_MAP_ROOT_SIZE])?; // data sm root - w.write_all(&vec![0; SPACE_MAP_ROOT_SIZE])?; // metadata sm root + w.write_all(&[0; SPACE_MAP_ROOT_SIZE])?; // data sm root + w.write_all(&[0; SPACE_MAP_ROOT_SIZE])?; // metadata sm root w.write_u64::(sb.mapping_root)?; w.write_u64::(sb.details_root)?; w.write_u32::(sb.data_block_size)?; diff --git a/src/version.rs b/src/version.rs index fe73777..19ea2d5 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1 +1,5 @@ -pub const TOOLS_VERSION: &str = include_str!("../VERSION"); +const TOOLS_VERSION: &str = include_str!("../VERSION"); + +pub fn tools_version() -> &'static str { + TOOLS_VERSION.trim_end() +} diff --git a/tests/cache_check.rs b/tests/cache_check.rs index 8d92e5d..41c81bb 100644 --- a/tests/cache_check.rs +++ b/tests/cache_check.rs @@ -1,6 +1,6 @@ use anyhow::Result; use duct::cmd; -use thinp::version::TOOLS_VERSION; +use thinp::version::tools_version; mod common; @@ -12,14 +12,14 @@ use common::*; #[test] fn accepts_v() -> Result<()> { let stdout = cache_check!("-V").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } #[test] fn accepts_version() -> Result<()> { let stdout = cache_check!("--version").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } diff --git a/tests/common/cache_xml_generator.rs b/tests/common/cache_xml_generator.rs index f7ebcdb..f198dea 100644 --- a/tests/common/cache_xml_generator.rs +++ b/tests/common/cache_xml_generator.rs @@ -24,8 +24,8 @@ pub fn write_xml(path: &Path, g: &mut dyn XmlGen) -> Result<()> { } pub struct CacheGen { - block_size: u64, - nr_cache_blocks: u64, + block_size: u32, + nr_cache_blocks: u32, nr_origin_blocks: u64, percent_resident: u8, percent_dirty: u8, @@ -33,8 +33,8 @@ pub struct CacheGen { impl CacheGen { pub fn new( - block_size: u64, - nr_cache_blocks: u64, + block_size: u32, + nr_cache_blocks: u32, nr_origin_blocks: u64, percent_resident: u8, percent_dirty: u8, @@ -67,7 +67,7 @@ impl XmlGen for CacheGen { v.mappings_b()?; { - let nr_resident = (self.nr_cache_blocks * 100 as u64) / (self.percent_resident as u64); + let nr_resident = (self.nr_cache_blocks * 100 as u32) / (self.percent_resident as u32); let mut used = HashSet::new(); for n in 0..nr_resident { let mut oblock = 0u64; diff --git a/tests/thin_check.rs b/tests/thin_check.rs index c31b31b..498612f 100644 --- a/tests/thin_check.rs +++ b/tests/thin_check.rs @@ -1,6 +1,6 @@ use anyhow::Result; use thinp::file_utils; -use thinp::version::TOOLS_VERSION; +use thinp::version::tools_version; mod common; @@ -13,14 +13,14 @@ use common::*; #[test] fn accepts_v() -> Result<()> { let stdout = thin_check!("-V").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } #[test] fn accepts_version() -> Result<()> { let stdout = thin_check!("--version").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } diff --git a/tests/thin_delta.rs b/tests/thin_delta.rs index 458a307..337ac57 100644 --- a/tests/thin_delta.rs +++ b/tests/thin_delta.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use thinp::version::TOOLS_VERSION; +use thinp::version::tools_version; mod common; use common::test_dir::*; @@ -10,14 +10,14 @@ use common::*; #[test] fn accepts_v() -> Result<()> { let stdout = thin_delta!("-V").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } #[test] fn accepts_version() -> Result<()> { let stdout = thin_delta!("--version").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } diff --git a/tests/thin_metadata_pack.rs b/tests/thin_metadata_pack.rs index 131cc95..84e3187 100644 --- a/tests/thin_metadata_pack.rs +++ b/tests/thin_metadata_pack.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use thinp::version::TOOLS_VERSION; +use thinp::version::tools_version; mod common; use common::test_dir::*; @@ -10,14 +10,14 @@ use common::*; #[test] fn accepts_v() -> Result<()> { let stdout = thin_metadata_pack!("-V").read()?; - assert!(stdout.contains(TOOLS_VERSION)); + assert!(stdout.contains(tools_version())); Ok(()) } #[test] fn accepts_version() -> Result<()> { let stdout = thin_metadata_pack!("--version").read()?; - assert!(stdout.contains(TOOLS_VERSION)); + assert!(stdout.contains(tools_version())); Ok(()) } diff --git a/tests/thin_metadata_unpack.rs b/tests/thin_metadata_unpack.rs index 33d3f0c..7a5d5ee 100644 --- a/tests/thin_metadata_unpack.rs +++ b/tests/thin_metadata_unpack.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use thinp::version::TOOLS_VERSION; +use thinp::version::tools_version; mod common; use common::test_dir::*; @@ -10,14 +10,14 @@ use common::*; #[test] fn accepts_v() -> Result<()> { let stdout = thin_metadata_unpack!("-V").read()?; - assert!(stdout.contains(TOOLS_VERSION)); + assert!(stdout.contains(tools_version())); Ok(()) } #[test] fn accepts_version() -> Result<()> { let stdout = thin_metadata_unpack!("--version").read()?; - assert!(stdout.contains(TOOLS_VERSION)); + assert!(stdout.contains(tools_version())); Ok(()) } diff --git a/tests/thin_repair.rs b/tests/thin_repair.rs index 19f7969..bc836d6 100644 --- a/tests/thin_repair.rs +++ b/tests/thin_repair.rs @@ -1,6 +1,6 @@ use anyhow::Result; use std::str::from_utf8; -use thinp::version::TOOLS_VERSION; +use thinp::version::tools_version; mod common; use common::test_dir::*; @@ -11,14 +11,14 @@ use common::*; #[test] fn accepts_v() -> Result<()> { let stdout = thin_repair!("-V").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } #[test] fn accepts_version() -> Result<()> { let stdout = thin_repair!("--version").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } diff --git a/tests/thin_restore.rs b/tests/thin_restore.rs index c8efa45..d2b85d0 100644 --- a/tests/thin_restore.rs +++ b/tests/thin_restore.rs @@ -1,7 +1,7 @@ use anyhow::Result; use std::str::from_utf8; use thinp::file_utils; -use thinp::version::TOOLS_VERSION; +use thinp::version::tools_version; mod common; use common::test_dir::*; @@ -12,14 +12,14 @@ use common::*; #[test] fn accepts_v() -> Result<()> { let stdout = thin_restore!("-V").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } #[test] fn accepts_version() -> Result<()> { let stdout = thin_restore!("--version").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } diff --git a/tests/thin_rmap.rs b/tests/thin_rmap.rs index 3797edf..7b2eaba 100644 --- a/tests/thin_rmap.rs +++ b/tests/thin_rmap.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use thinp::version::TOOLS_VERSION; +use thinp::version::tools_version; mod common; use common::test_dir::*; @@ -10,14 +10,14 @@ use common::*; #[test] fn accepts_v() -> Result<()> { let stdout = thin_rmap!("-V").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) } #[test] fn accepts_version() -> Result<()> { let stdout = thin_rmap!("--version").read()?; - assert_eq!(stdout, TOOLS_VERSION); + assert_eq!(stdout, tools_version()); Ok(()) }