Merge pull request #170 from mingnus/rust-cache-tools

Merge recent changes in cache-tools
This commit is contained in:
Joe Thornber 2021-05-12 09:29:49 +01:00 committed by GitHub
commit 11c354b3b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1587 additions and 607 deletions

13
.pre-commit-config.yaml Normal file
View 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
View File

@ -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"

View File

@ -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"

View File

@ -6,6 +6,7 @@
#include <boost/tuple/tuple.hpp>
#include <boost/variant.hpp>
#include <memory>
#include <vector>
#include <string>

View File

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

View File

@ -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"),

View File

@ -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")

View File

@ -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")

View File

@ -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")

View 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")

View File

@ -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")

View File

@ -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
View File

@ -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
View 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
View File

@ -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,
},
))
}

View File

@ -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
View File

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

View File

@ -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
View File

@ -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>,
}

View File

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

View File

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

View File

@ -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")),
}

View File

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

View File

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

View File

@ -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
View 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))
}

View File

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

View File

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

View File

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

View File

@ -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;

View 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(())
}
//------------------------------------------

View File

@ -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 {

View File

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

View File

@ -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::*;

View File

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

View File

@ -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)?;

View File

@ -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()
}

View File

@ -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(())
}

View File

@ -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;

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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(())
}