[cache_check (rust)] Implement basic functions

This commit is contained in:
Ming-Hung Tsai 2020-12-20 20:23:30 +08:00
parent faa371c208
commit 2bb3bf65b7
3 changed files with 229 additions and 0 deletions

62
src/bin/cache_check.rs Normal file
View File

@ -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);
}
}
//------------------------------------------

166
src/cache/check.rs vendored Normal file
View File

@ -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<Mutex<BTreeSet<u64>>>,
}
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<Mapping> 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<Width> {
_not_used: PhantomData<Width>,
}
impl<Width> CheckHintVisitor<Width> {
fn new() -> CheckHintVisitor<Width> {
CheckHintVisitor {
_not_used: PhantomData,
}
}
}
impl<Width: typenum::Unsigned> ArrayBlockVisitor<Hint<Width>> for CheckHintVisitor<Width> {
fn visit(&self, _index: u64, _hint: Hint<Width>) -> 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<dyn IoEngine + Send + Sync>,
}
fn mk_context(opts: &CacheCheckOptions) -> anyhow::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 {
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::<Width>::new());
w.walk(c, sb.hint_root)?;
}
if !opts.skip_discards {
// TODO: check discard bitset
}
Ok(())
}
//------------------------------------------

1
src/cache/mod.rs vendored
View File

@ -1,3 +1,4 @@
pub mod check;
pub mod hint;
pub mod mapping;
pub mod superblock;