diff --git a/src/bin/cache_check.rs b/src/bin/cache_check.rs new file mode 100644 index 0000000..4fed20c --- /dev/null +++ b/src/bin/cache_check.rs @@ -0,0 +1,62 @@ +extern crate clap; +extern crate thinp; + +use clap::{App, Arg}; +use std::path::Path; +use thinp::cache::check::{check, CacheCheckOptions}; + +//------------------------------------------ + +fn main() { + let parser = App::new("cache_check") + .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("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 array") + .long("skip-mappings") + .value_name("SKIP_MAPPINGS"), + ) + .arg( + Arg::with_name("SKIP_HINTS") + .help("Don't check the hint array") + .long("skip-hints") + .value_name("SKIP_HINTS"), + ) + .arg( + Arg::with_name("SKIP_DISCARDS") + .help("Don't check the discard bitset") + .long("skip-discards") + .value_name("SKIP_DISCARDS"), + ); + + let matches = parser.get_matches(); + let input_file = Path::new(matches.value_of("INPUT").unwrap()); + + let opts = CacheCheckOptions { + dev: &input_file, + async_io: false, + sb_only: matches.is_present("SB_ONLY"), + skip_mappings: matches.is_present("SKIP_MAPPINGS"), + skip_hints: matches.is_present("SKIP_HINTS"), + skip_discards: matches.is_present("SKIP_DISCARDS"), + }; + + if let Err(reason) = check(opts) { + eprintln!("{}", reason); + std::process::exit(1); + } +} + +//------------------------------------------ diff --git a/src/cache/check.rs b/src/cache/check.rs new file mode 100644 index 0000000..92895e1 --- /dev/null +++ b/src/cache/check.rs @@ -0,0 +1,166 @@ +use anyhow::anyhow; +use std::marker::PhantomData; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use std::collections::*; + +use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine}; +use crate::cache::hint::*; +use crate::cache::mapping::*; +use crate::cache::superblock::*; +use crate::pdata::array_walker::*; + +//------------------------------------------ + +const MAX_CONCURRENT_IO: u32 = 1024; + +//------------------------------------------ + +struct CheckMappingVisitor { + allowed_flags: u32, + seen_oblocks: Arc>>, +} + +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; + } + CheckMappingVisitor { + allowed_flags: flags, + seen_oblocks: Arc::new(Mutex::new(BTreeSet::new())), + } + } + + fn seen_oblock(&self, b: u64) -> bool { + let seen_oblocks = self.seen_oblocks.lock().unwrap(); + return seen_oblocks.contains(&b); + } + + fn record_oblock(&self, b: u64) { + let mut seen_oblocks = self.seen_oblocks.lock().unwrap(); + seen_oblocks.insert(b); + } + + // 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; + } +} + +impl ArrayBlockVisitor for CheckMappingVisitor { + fn visit(&self, _index: u64, m: Mapping) -> anyhow::Result<()> { + if !m.is_valid() { + return Ok(()); + } + + if self.seen_oblock(m.oblock) { + return Err(anyhow!("origin block already mapped")); + } + + self.record_oblock(m.oblock); + + if !self.has_unknown_flags(&m) { + return Err(anyhow!("unknown flags in mapping")); + } + + Ok(()) + } +} + +//------------------------------------------ + +struct CheckHintVisitor { + _not_used: PhantomData, +} + +impl CheckHintVisitor { + fn new() -> CheckHintVisitor { + CheckHintVisitor { + _not_used: PhantomData, + } + } +} + +impl ArrayBlockVisitor> for CheckHintVisitor { + fn visit(&self, _index: u64, _hint: Hint) -> anyhow::Result<()> { + // TODO: check hints + Ok(()) + } +} + +//------------------------------------------ + +// TODO: ignore_non_fatal, clear_needs_check, auto_repair +pub struct CacheCheckOptions<'a> { + pub dev: &'a Path, + pub async_io: bool, + pub sb_only: bool, + pub skip_mappings: bool, + pub skip_hints: bool, + pub skip_discards: bool, +} + +// TODO: thread pool, report +struct Context { + engine: Arc, +} + +fn mk_context(opts: &CacheCheckOptions) -> 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, + }) +} + +pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> { + let ctx = mk_context(&opts)?; + + let engine = &ctx.engine; + + let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?; + + if opts.sb_only { + return Ok(()); + } + + // 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 + } + } + + 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_discards { + // TODO: check discard bitset + } + + Ok(()) +} + +//------------------------------------------ diff --git a/src/cache/mod.rs b/src/cache/mod.rs index f1cf219..a120ce6 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -1,3 +1,4 @@ +pub mod check; pub mod hint; pub mod mapping; pub mod superblock;