Merge pull request #170 from mingnus/rust-cache-tools
Merge recent changes in cache-tools
This commit is contained in:
commit
11c354b3b1
13
.pre-commit-config.yaml
Normal file
13
.pre-commit-config.yaml
Normal file
@ -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
|
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
@ -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) {
|
||||
|
41
src/bin/cache_dump.rs
Normal file
41
src/bin/cache_dump.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------
|
@ -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<dyn IoEngine + Send + Sync>;
|
||||
|
||||
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"),
|
||||
|
@ -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")
|
||||
|
@ -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<Vec<u64>>) -> 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")
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
328
src/cache/check.rs
vendored
328
src/cache/check.rs
vendored
@ -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<Mutex<BTreeSet<u64>>>,
|
||||
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<BTreeSet<u64>>,
|
||||
}
|
||||
|
||||
impl MappingChecker {
|
||||
pub fn new(nr_origin_blocks: Option<u64>) -> 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<Mapping> for MappingChecker {
|
||||
fn visit(&self, _index: u64, b: ArrayBlock<Mapping>) -> array::Result<()> {
|
||||
let mut errs: Vec<ArrayError> = 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<Mapping> 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<Inner>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
seen_oblocks: BTreeSet<u64>,
|
||||
dirty_bits: CheckedBitSet,
|
||||
}
|
||||
|
||||
impl MappingChecker {
|
||||
pub fn new(nr_origin_blocks: Option<u64>, 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<bool>) -> 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<u64>) -> 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<Mapping> for MappingChecker {
|
||||
fn visit(&self, index: u64, b: ArrayBlock<Mapping>) -> array::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
let mut errs: Vec<ArrayError> = 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<Width> {
|
||||
_not_used: PhantomData<Width>,
|
||||
}
|
||||
struct HintChecker;
|
||||
|
||||
impl<Width> CheckHintVisitor<Width> {
|
||||
fn new() -> CheckHintVisitor<Width> {
|
||||
CheckHintVisitor {
|
||||
_not_used: PhantomData,
|
||||
}
|
||||
impl HintChecker {
|
||||
fn new() -> HintChecker {
|
||||
HintChecker
|
||||
}
|
||||
}
|
||||
|
||||
impl<Width: typenum::Unsigned> ArrayBlockVisitor<Hint<Width>> for CheckHintVisitor<Width> {
|
||||
fn visit(&self, _index: u64, _hint: Hint<Width>) -> anyhow::Result<()> {
|
||||
impl ArrayVisitor<Hint> for HintChecker {
|
||||
fn visit(&self, _index: u64, _b: ArrayBlock<Hint>) -> array::Result<()> {
|
||||
// TODO: check hints
|
||||
Ok(())
|
||||
}
|
||||
@ -93,7 +222,7 @@ impl<Width: typenum::Unsigned> ArrayBlockVisitor<Hint<Width>> 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<Report>,
|
||||
}
|
||||
|
||||
// TODO: thread pool, report
|
||||
// TODO: thread pool
|
||||
struct Context {
|
||||
report: Arc<Report>,
|
||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||
}
|
||||
|
||||
@ -118,40 +251,113 @@ fn mk_context(opts: &CacheCheckOptions) -> anyhow::Result<Context> {
|
||||
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::<Width>::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::<SMRoot>(&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(())
|
||||
|
317
src/cache/dump.rs
vendored
Normal file
317
src/cache/dump.rs
vendored
Normal file
@ -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<Inner<'a>>,
|
||||
}
|
||||
|
||||
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<Mapping> for MappingEmitter<'a> {
|
||||
fn visit(&self, index: u64, b: ArrayBlock<Mapping>) -> 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<FixedBitSet>,
|
||||
}
|
||||
|
||||
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<u64> for DirtyVisitor {
|
||||
fn visit(&self, index: u64, b: ArrayBlock<u64>) -> 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<Inner<'a>>,
|
||||
}
|
||||
|
||||
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<Mapping> for MappingEmitter<'a> {
|
||||
fn visit(&self, index: u64, b: ArrayBlock<Mapping>) -> 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<Hint> for HintEmitter<'a> {
|
||||
fn visit(&self, index: u64, b: ArrayBlock<Hint>) -> 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<dyn IoEngine + Send + Sync>,
|
||||
}
|
||||
|
||||
fn mk_context(opts: &CacheDumpOptions) -> 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 })
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
//------------------------------------------
|
16
src/cache/hint.rs
vendored
16
src/cache/hint.rs
vendored
@ -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<Width> {
|
||||
pub hint: [u8; 4], // FIXME: support various hint sizes
|
||||
_not_used: PhantomData<Width>,
|
||||
pub struct Hint {
|
||||
pub hint: [u8; 4],
|
||||
}
|
||||
|
||||
impl<Width: typenum::Unsigned> Unpack for Hint<Width> {
|
||||
impl Unpack for Hint {
|
||||
fn disk_size() -> u32 {
|
||||
Width::to_u32()
|
||||
4
|
||||
}
|
||||
|
||||
// FIXME: support different width
|
||||
fn unpack(i: &[u8]) -> IResult<&[u8], Hint<Width>> {
|
||||
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,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
9
src/cache/mapping.rs
vendored
9
src/cache/mapping.rs
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
1
src/cache/mod.rs
vendored
1
src/cache/mod.rs
vendored
@ -1,4 +1,5 @@
|
||||
pub mod check;
|
||||
pub mod dump;
|
||||
pub mod hint;
|
||||
pub mod mapping;
|
||||
pub mod superblock;
|
||||
|
6
src/cache/superblock.rs
vendored
6
src/cache/superblock.rs
vendored
@ -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,
|
||||
|
8
src/cache/xml.rs
vendored
8
src/cache/xml.rs
vendored
@ -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<u8>,
|
||||
}
|
||||
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -114,4 +114,8 @@ pub fn pack_index<W: Write>(w: &mut W, bytes: &[u8]) -> PResult<()> {
|
||||
io_to_pr(pack_literal(w, bytes))
|
||||
}
|
||||
|
||||
pub fn pack_array<W: Write>(w: &mut W, bytes: &[u8]) -> PResult<()> {
|
||||
io_to_pr(pack_literal(w, bytes))
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
@ -209,6 +209,7 @@ fn pack_block<W: Write>(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")),
|
||||
}
|
||||
|
||||
|
@ -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<V: Unpack> {
|
||||
pub header: ArrayBlockHeader,
|
||||
pub values: Vec<V>,
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
|
||||
#[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<ArrayError>),
|
||||
|
||||
//#[error("aggregate: {0:?}")]
|
||||
Aggregate(Vec<ArrayError>),
|
||||
|
||||
//#[error("{0:?}, {1}")]
|
||||
Path(Vec<u64>, Box<ArrayError>),
|
||||
|
||||
#[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 {
|
||||
ArrayError::Aggregate(errs)
|
||||
}
|
||||
|
||||
impl ArrayError {
|
||||
pub fn index_context(self, index: u64) -> ArrayError {
|
||||
ArrayError::IndexContext(index, Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ArrayError>;
|
||||
|
||||
//------------------------------------------
|
||||
|
||||
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<V: Unpack>(path: &[u64], data: &[u8]) -> Result<ArrayBlock<V>> {
|
||||
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 })
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
|
@ -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<V: Unpack> {
|
||||
pub header: ArrayBlockHeader,
|
||||
pub values: Vec<V>,
|
||||
}
|
||||
|
||||
impl<V: Unpack> ArrayBlock<V> {
|
||||
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<V: Unpack>(data: &[u8]) -> Result<ArrayBlock<V>> {
|
||||
// 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 })
|
||||
}
|
||||
|
||||
//------------------------------------------
|
@ -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<dyn IoEngine + Send + Sync>,
|
||||
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||
ignore_non_fatal: bool,
|
||||
}
|
||||
|
||||
// FIXME: define another Result type for array visiting?
|
||||
pub trait ArrayBlockVisitor<V: Unpack> {
|
||||
fn visit(&self, index: u64, v: V) -> anyhow::Result<()>;
|
||||
pub trait ArrayVisitor<V: Unpack> {
|
||||
fn visit(&self, index: u64, b: ArrayBlock<V>) -> array::Result<()>;
|
||||
}
|
||||
|
||||
struct BlockValueVisitor<V> {
|
||||
//------------------------------------------
|
||||
|
||||
// FIXME: Eliminate this structure by impl NodeVisitor for ArrayWalker?
|
||||
struct BlockValueVisitor<'a, V> {
|
||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||
array_block_visitor: Box<dyn ArrayBlockVisitor<V>>,
|
||||
array_visitor: &'a mut dyn ArrayVisitor<V>,
|
||||
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||
array_errs: Mutex<Vec<ArrayError>>,
|
||||
}
|
||||
|
||||
impl<V: Unpack + Copy> BlockValueVisitor<V> {
|
||||
pub fn new<Visitor: 'static + ArrayBlockVisitor<V>>(
|
||||
impl<'a, V: Unpack + Copy> BlockValueVisitor<'a, V> {
|
||||
pub fn new(
|
||||
e: Arc<dyn IoEngine + Send + Sync>,
|
||||
v: Box<Visitor>,
|
||||
) -> BlockValueVisitor<V> {
|
||||
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||
v: &'a mut dyn ArrayVisitor<V>,
|
||||
) -> BlockValueVisitor<'a, V> {
|
||||
BlockValueVisitor {
|
||||
engine: e,
|
||||
array_block_visitor: v,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visit_array_block(&self, index: u64, array_block: ArrayBlock<V>) {
|
||||
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<V: Unpack + Copy> NodeVisitor<ArrayBlockEntry> for BlockValueVisitor<V> {
|
||||
// FIXME: return errors
|
||||
impl<'a, V: Unpack + Copy> NodeVisitor<u64> 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::<V>(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::<V>(&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<dyn IoEngine + Send + Sync>, 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<BV, V: Copy>(&self, visitor: Box<BV>, root: u64) -> Result<()>
|
||||
pub fn new_with_sm(
|
||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||
ignore_non_fatal: bool,
|
||||
) -> array::Result<ArrayWalker> {
|
||||
{
|
||||
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<V>(&self, visitor: &mut dyn ArrayVisitor<V>, root: u64) -> array::Result<()>
|
||||
where
|
||||
BV: 'static + ArrayBlockVisitor<V>,
|
||||
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::<V>::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::<V>::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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
109
src/pdata/bitset.rs
Normal file
109
src/pdata/bitset.rs
Normal file
@ -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<bool> {
|
||||
if !self.bits.contains(bit << 1) {
|
||||
return None;
|
||||
}
|
||||
Some(self.bits.contains((bit << 1) + 1))
|
||||
}
|
||||
}
|
||||
|
||||
struct BitsetVisitor {
|
||||
nr_bits: usize,
|
||||
bits: Mutex<CheckedBitSet>,
|
||||
}
|
||||
|
||||
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<u64> for BitsetVisitor {
|
||||
fn visit(&self, index: u64, b: ArrayBlock<u64>) -> 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<dyn IoEngine + Send + Sync>,
|
||||
root: u64,
|
||||
nr_bits: usize,
|
||||
ignore_none_fatal: bool,
|
||||
) -> (CheckedBitSet, Option<array::ArrayError>) {
|
||||
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<dyn IoEngine + Send + Sync>,
|
||||
root: u64,
|
||||
nr_bits: usize,
|
||||
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||
ignore_none_fatal: bool,
|
||||
) -> array::Result<(CheckedBitSet, Option<array::ArrayError>)> {
|
||||
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))
|
||||
}
|
@ -560,7 +560,10 @@ pub fn unpack_node<V: Unpack>(
|
||||
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),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ pub struct WriteResult {
|
||||
/// Write a node to a free metadata block.
|
||||
fn write_node_<V: Unpack + Pack>(w: &mut WriteBatcher, mut node: Node<V>) -> Result<WriteResult> {
|
||||
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<V> {
|
||||
/// 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<NodeSummary>) -> 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<V> {
|
||||
}
|
||||
|
||||
// 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<V> {
|
||||
pub fn complete(mut self, w: &mut WriteBatcher) -> Result<Vec<NodeSummary>> {
|
||||
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<V> {
|
||||
|
||||
self.emit_all(w)?;
|
||||
|
||||
if self.nodes.len() == 0 {
|
||||
if self.nodes.is_empty() {
|
||||
self.emit_empty_leaf(w)?
|
||||
}
|
||||
|
||||
@ -461,7 +461,7 @@ impl<V: Unpack + Pack + Clone> Builder<V> {
|
||||
self.leaf_builder.push_value(w, k, v)
|
||||
}
|
||||
|
||||
pub fn push_leaves(&mut self, w: &mut WriteBatcher, leaves: &Vec<NodeSummary>) -> Result<()> {
|
||||
pub fn push_leaves(&mut self, w: &mut WriteBatcher, leaves: &[NodeSummary]) -> Result<()> {
|
||||
self.leaf_builder.push_nodes(w, leaves)
|
||||
}
|
||||
|
||||
|
@ -69,10 +69,7 @@ impl BTreeWalker {
|
||||
|
||||
fn failed(&self, b: u64) -> Option<BTreeError> {
|
||||
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<V: Unpack + Copy>(
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
|
||||
struct NoopVisitor<V> {
|
||||
dummy: std::marker::PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<V> NoopVisitor<V> {
|
||||
pub fn new() -> NoopVisitor<V> {
|
||||
NoopVisitor {
|
||||
dummy: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Unpack> NodeVisitor<V> for NoopVisitor<V> {
|
||||
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<V: Unpack>(
|
||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||
path: &mut Vec<u64>,
|
||||
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::<V>::new();
|
||||
w.walk(path, &v, root)
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
|
@ -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;
|
||||
|
323
src/pdata/space_map_checker.rs
Normal file
323
src/pdata/space_map_checker.rs
Normal file
@ -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<u32> 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<dyn IoEngine + Send + Sync>,
|
||||
report: Arc<Report>,
|
||||
kind: &str,
|
||||
entries: Vec<IndexEntry>,
|
||||
sm: ASpaceMap,
|
||||
) -> Result<Vec<BitmapLeak>> {
|
||||
// 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::<Bitmap>(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<dyn IoEngine + Send + Sync>,
|
||||
bitmap_root: u64,
|
||||
metadata_sm: ASpaceMap,
|
||||
ignore_non_fatal: bool,
|
||||
) -> Result<Vec<IndexEntry>> {
|
||||
let entries_map = btree_to_map_with_sm::<IndexEntry>(
|
||||
&mut vec![0],
|
||||
engine,
|
||||
metadata_sm.clone(),
|
||||
ignore_non_fatal,
|
||||
bitmap_root,
|
||||
)?;
|
||||
|
||||
let entries: Vec<IndexEntry> = entries_map.values().cloned().collect();
|
||||
inc_entries(&metadata_sm, &entries[0..])?;
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn gather_metadata_index_entries(
|
||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||
bitmap_root: u64,
|
||||
metadata_sm: ASpaceMap,
|
||||
) -> Result<Vec<IndexEntry>> {
|
||||
let b = engine.read(bitmap_root)?;
|
||||
let entries = unpack::<MetadataIndex>(b.get_data())?.indexes;
|
||||
|
||||
// Filter out unused entries with block 0
|
||||
let entries: Vec<IndexEntry> = 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<dyn IoEngine + Send + Sync>,
|
||||
report: Arc<Report>,
|
||||
root: SMRoot,
|
||||
disk_sm: ASpaceMap,
|
||||
metadata_sm: ASpaceMap,
|
||||
ignore_non_fatal: bool,
|
||||
) -> Result<Vec<BitmapLeak>> {
|
||||
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<dyn IoEngine + Send + Sync>,
|
||||
report: Arc<Report>,
|
||||
root: SMRoot,
|
||||
metadata_sm: ASpaceMap,
|
||||
ignore_non_fatal: bool,
|
||||
) -> Result<Vec<BitmapLeak>> {
|
||||
count_btree_blocks::<u32>(
|
||||
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<dyn IoEngine + Send + Sync>,
|
||||
entries: Vec<BitmapLeak>,
|
||||
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::<Bitmap>(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(())
|
||||
}
|
||||
|
||||
//------------------------------------------
|
@ -371,7 +371,7 @@ pub fn write_metadata_sm(w: &mut WriteBatcher, sm: &dyn SpaceMap) -> Result<SMRo
|
||||
let mut by_bitmap = BTreeMap::new();
|
||||
for b in allocations {
|
||||
let bitmap = block_to_bitmap(b);
|
||||
(*by_bitmap.entry(bitmap).or_insert(Vec::new())).push(b % ENTRIES_PER_BITMAP as u64);
|
||||
(*by_bitmap.entry(bitmap).or_insert_with(Vec::new)).push(b % ENTRIES_PER_BITMAP as u64);
|
||||
}
|
||||
|
||||
for (bitmap, allocs) in by_bitmap {
|
||||
|
@ -1,16 +1,15 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Cursor;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::{self, JoinHandle};
|
||||
use threadpool::ThreadPool;
|
||||
|
||||
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_checker::*;
|
||||
use crate::pdata::space_map_disk::*;
|
||||
use crate::pdata::unpack::*;
|
||||
use crate::report::*;
|
||||
@ -72,211 +71,6 @@ impl NodeVisitor<BlockTime> 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<u32> 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<u64>,
|
||||
ctx: &Context,
|
||||
kind: &str,
|
||||
entries: Vec<IndexEntry>,
|
||||
metadata_sm: Option<ASpaceMap>,
|
||||
sm: ASpaceMap,
|
||||
root: SMRoot,
|
||||
) -> Result<Vec<BitmapLeak>> {
|
||||
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::<Bitmap>(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<BitmapLeak>, 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::<Bitmap>(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::<SMRoot>(&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::<SMRoot>(&sb.data_sm_root[0..])?;
|
||||
|
||||
let entries = btree_to_map_with_sm::<IndexEntry>(
|
||||
&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<IndexEntry> = 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::<SMRoot>(&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::<MetadataIndex>(b.get_data())?.indexes;
|
||||
|
||||
// Unused entries will point to block 0
|
||||
let entries: Vec<IndexEntry> = 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::<u32>(
|
||||
&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<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||
}
|
||||
|
||||
pub fn check_with_maps(engine: Arc<dyn IoEngine + Send + Sync>, report: Arc<Report>) -> Result<CheckMaps> {
|
||||
pub fn check_with_maps(
|
||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||
report: Arc<Report>,
|
||||
) -> Result<CheckMaps> {
|
||||
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<dyn IoEngine + Send + Sync>, report: Arc<Repo
|
||||
report.info(&format!("TRANSACTION_ID={}", sb.transaction_id));
|
||||
|
||||
let metadata_root = unpack::<SMRoot>(&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::<DeviceDetail>(
|
||||
&mut path,
|
||||
engine.clone(),
|
||||
false,
|
||||
sb.details_root,
|
||||
)?;
|
||||
let devs = btree_to_map::<DeviceDetail>(&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<dyn IoEngine + Send + Sync>, report: Arc<Repo
|
||||
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::<SMRoot>(&sb.data_sm_root[0..])?;
|
||||
|
||||
let entries = btree_to_map_with_sm::<IndexEntry>(
|
||||
&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<IndexEntry> = 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::<SMRoot>(&sb.metadata_sm_root[0..])?;
|
||||
report.info(&format!(
|
||||
@ -671,40 +424,13 @@ pub fn check_with_maps(engine: Arc<dyn IoEngine + Send + Sync>, report: Arc<Repo
|
||||
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::<MetadataIndex>(b.get_data())?.indexes;
|
||||
|
||||
// Unused entries will point to block 0
|
||||
let entries: Vec<IndexEntry> = 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::<u32>(
|
||||
&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<dyn IoEngine + Send + Sync>, report: Arc<Repo
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
|
@ -255,8 +255,7 @@ fn collect_leaves(
|
||||
let mut w = LeafWalker::new(ctx.engine.clone(), sm.deref_mut(), false);
|
||||
let mut v = CollectLeaves::new();
|
||||
|
||||
let mut path = Vec::new();
|
||||
path.push(0);
|
||||
let mut path = vec![0];
|
||||
|
||||
// ctx.report.set_title(&format!("collecting {}", *r));
|
||||
w.walk::<CollectLeaves, BlockTime>(&mut path, &mut v, *r)?;
|
||||
@ -323,8 +322,7 @@ fn build_metadata(ctx: &Context, sb: &Superblock) -> Result<Metadata> {
|
||||
report.set_title("Reading superblock");
|
||||
//let metadata_root = unpack::<SMRoot>(&sb.metadata_sm_root[0..])?;
|
||||
//let data_root = unpack::<SMRoot>(&sb.data_sm_root[0..])?;
|
||||
let mut path = Vec::new();
|
||||
path.push(0);
|
||||
let mut path = vec![0];
|
||||
|
||||
report.set_title("Reading device details");
|
||||
let details = btree_to_map::<DeviceDetail>(&mut path, engine.clone(), true, sb.details_root)?;
|
||||
@ -385,6 +383,7 @@ fn build_metadata(ctx: &Context, sb: &Superblock) -> Result<Metadata> {
|
||||
|
||||
//------------------------------------------
|
||||
|
||||
#[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<u64, Vec<u64>>, es: &[Entry]) -> Vec<Entry> {
|
||||
use Entry::*;
|
||||
|
||||
@ -427,6 +427,7 @@ fn entries_to_runs(runs: &BTreeMap<u64, Vec<u64>>, es: &[Entry]) -> Vec<Entry> {
|
||||
|
||||
// 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<Metadata> {
|
||||
use Entry::*;
|
||||
|
||||
|
@ -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 </def> tag");
|
||||
let msg = "Unbalanced </def> 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 <def> or <device> tag.");
|
||||
let msg = "Mapping tags must appear within a <def> or <device> tag.".to_string();
|
||||
Err(anyhow!(msg))
|
||||
}
|
||||
}
|
||||
|
@ -110,14 +110,14 @@ fn pack_superblock<W: WriteBytesExt>(sb: &Superblock, w: &mut W) -> Result<()> {
|
||||
}
|
||||
|
||||
w.write_u64::<LittleEndian>(sb.block)?;
|
||||
w.write_all(&vec![0; UUID_SIZE])?;
|
||||
w.write_all(&[0; UUID_SIZE])?;
|
||||
w.write_u64::<LittleEndian>(MAGIC)?;
|
||||
w.write_u32::<LittleEndian>(sb.version)?;
|
||||
w.write_u32::<LittleEndian>(sb.time)?;
|
||||
w.write_u64::<LittleEndian>(sb.transaction_id)?;
|
||||
w.write_u64::<LittleEndian>(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::<LittleEndian>(sb.mapping_root)?;
|
||||
w.write_u64::<LittleEndian>(sb.details_root)?;
|
||||
w.write_u32::<LittleEndian>(sb.data_block_size)?;
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user