Merge pull request #188 from mingnus/2021-08-05-thin-repair-find-roots

Support broken superblock in thin_repair
This commit is contained in:
Joe Thornber 2021-09-10 13:59:16 +01:00 committed by GitHub
commit 9aa36f017a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1032 additions and 45 deletions

View File

@ -30,7 +30,7 @@ fn main() {
.help("Specify the output file rather than stdout")
.short("o")
.long("output")
.value_name("OUTPUT"),
.value_name("FILE"),
)
// arguments
.arg(

View File

@ -34,7 +34,7 @@ fn main() {
.help("Specify the input device")
.short("i")
.long("input")
.value_name("INPUT")
.value_name("FILE")
.required(true),
)
.arg(
@ -42,7 +42,7 @@ fn main() {
.help("Specify the output device")
.short("o")
.long("output")
.value_name("OUTPUT")
.value_name("FILE")
.required(true),
);

View File

@ -41,7 +41,7 @@ fn main() {
.help("Specify the input xml")
.short("i")
.long("input")
.value_name("INPUT")
.value_name("FILE")
.required(true),
)
.arg(
@ -49,7 +49,7 @@ fn main() {
.help("Specify the output device to check")
.short("o")
.long("output")
.value_name("OUTPUT")
.value_name("FILE")
.required(true),
);

View File

@ -10,6 +10,7 @@ use std::sync::Arc;
use thinp::file_utils;
use thinp::report::*;
use thinp::thin::dump::{dump, ThinDumpOptions};
use thinp::thin::metadata_repair::SuperblockOverrides;
fn main() {
let parser = App::new("thin_dump")
@ -40,6 +41,12 @@ fn main() {
.long("skip-mappings"),
)
// options
.arg(
Arg::with_name("DATA_BLOCK_SIZE")
.help("Provide the data block size for repairing")
.long("data-block-size")
.value_name("SECTORS"),
)
.arg(
Arg::with_name("METADATA_SNAPSHOT")
.help("Access the metadata snapshot on a live pool")
@ -47,12 +54,24 @@ fn main() {
.long("metadata-snapshot")
.value_name("METADATA_SNAPSHOT"),
)
.arg(
Arg::with_name("NR_DATA_BLOCKS")
.help("Override the number of data blocks if needed")
.long("nr-data-blocks")
.value_name("NUM"),
)
.arg(
Arg::with_name("OUTPUT")
.help("Specify the output file rather than stdout")
.short("o")
.long("output")
.value_name("OUTPUT"),
.value_name("FILE"),
)
.arg(
Arg::with_name("TRANSACTION_ID")
.help("Override the transaction id if needed")
.long("transaction-id")
.value_name("NUM"),
)
// arguments
.arg(
@ -75,6 +94,27 @@ fn main() {
exit(1);
}
let transaction_id = matches.value_of("TRANSACTION_ID").map(|s| {
s.parse::<u64>().unwrap_or_else(|_| {
eprintln!("Couldn't parse transaction_id");
exit(1);
})
});
let data_block_size = matches.value_of("DATA_BLOCK_SIZE").map(|s| {
s.parse::<u32>().unwrap_or_else(|_| {
eprintln!("Couldn't parse data_block_size");
exit(1);
})
});
let nr_data_blocks = matches.value_of("NR_DATA_BLOCKS").map(|s| {
s.parse::<u64>().unwrap_or_else(|_| {
eprintln!("Couldn't parse nr_data_blocks");
exit(1);
})
});
let report;
if matches.is_present("QUIET") {
@ -90,6 +130,12 @@ fn main() {
output: output_file,
async_io: matches.is_present("ASYNC_IO"),
report,
repair: matches.is_present("REPAIR"),
overrides: SuperblockOverrides {
transaction_id,
data_block_size,
nr_data_blocks,
},
};
if let Err(reason) = dump(opts) {

View File

@ -9,6 +9,7 @@ use std::process::exit;
use std::sync::Arc;
use thinp::file_utils;
use thinp::report::*;
use thinp::thin::metadata_repair::SuperblockOverrides;
use thinp::thin::repair::{repair, ThinRepairOptions};
fn main() {
@ -29,28 +30,39 @@ fn main() {
.long("quiet"),
)
// options
.arg(
Arg::with_name("DATA_BLOCK_SIZE")
.help("Provide the data block size for repairing")
.long("data-block-size")
.value_name("SECTORS"),
)
.arg(
Arg::with_name("INPUT")
.help("Specify the input device")
.short("i")
.long("input")
.value_name("INPUT")
.value_name("FILE")
.required(true),
)
.arg(
Arg::with_name("NR_DATA_BLOCKS")
.help("Override the number of data blocks if needed")
.long("nr-data-blocks")
.value_name("NUM"),
)
.arg(
Arg::with_name("OUTPUT")
.help("Specify the output device")
.short("o")
.long("output")
.value_name("OUTPUT")
.value_name("FILE")
.required(true),
)
.arg(
Arg::with_name("OVERRIDE_MAPPING_ROOT")
.help("Specify a mapping root to use")
.long("override-mapping-root")
.value_name("OVERRIDE_MAPPING_ROOT")
.takes_value(true),
Arg::with_name("TRANSACTION_ID")
.help("Override the transaction id if needed")
.long("transaction-id")
.value_name("NUM"),
);
let matches = parser.get_matches();
@ -62,6 +74,27 @@ fn main() {
exit(1);
}
let transaction_id = matches.value_of("TRANSACTION_ID").map(|s| {
s.parse::<u64>().unwrap_or_else(|_| {
eprintln!("Couldn't parse transaction_id");
exit(1);
})
});
let data_block_size = matches.value_of("DATA_BLOCK_SIZE").map(|s| {
s.parse::<u32>().unwrap_or_else(|_| {
eprintln!("Couldn't parse data_block_size");
exit(1);
})
});
let nr_data_blocks = matches.value_of("NR_DATA_BLOCKS").map(|s| {
s.parse::<u64>().unwrap_or_else(|_| {
eprintln!("Couldn't parse nr_data_blocks");
exit(1);
})
});
let report;
if matches.is_present("QUIET") {
@ -77,6 +110,11 @@ fn main() {
output: &output_file,
async_io: matches.is_present("ASYNC_IO"),
report,
overrides: SuperblockOverrides {
transaction_id,
data_block_size,
nr_data_blocks,
},
};
if let Err(reason) = repair(opts) {

View File

@ -34,7 +34,7 @@ fn main() {
.help("Specify the input xml")
.short("i")
.long("input")
.value_name("INPUT")
.value_name("FILE")
.required(true),
)
.arg(
@ -42,7 +42,7 @@ fn main() {
.help("Specify the output device")
.short("o")
.long("output")
.value_name("OUTPUT")
.value_name("FILE")
.required(true),
)
.arg(

View File

@ -20,7 +20,7 @@ fn main() {
.required(true)
.short("i")
.long("input")
.value_name("INPUT")
.value_name("FILE")
.takes_value(true),
)
.arg(
@ -29,7 +29,7 @@ fn main() {
.required(true)
.short("o")
.long("output")
.value_name("OUTPUT")
.value_name("FILE")
.takes_value(true),
)
.arg(

View File

@ -2,7 +2,6 @@ use anyhow::Result;
use std::convert::TryInto;
use std::fs::OpenOptions;
use std::io::Cursor;
use std::path::Path;
use std::sync::Arc;
@ -14,8 +13,8 @@ use crate::cache::xml;
use crate::io_engine::*;
use crate::math::*;
use crate::pdata::array_builder::*;
use crate::pdata::space_map_common::pack_root;
use crate::pdata::space_map_metadata::*;
use crate::pdata::unpack::Pack;
use crate::report::*;
use crate::write_batcher::*;
@ -240,10 +239,8 @@ impl<'a> MetadataVisitor for Restorer<'a> {
//------------------------------------------
fn build_metadata_sm(w: &mut WriteBatcher) -> Result<Vec<u8>> {
let mut sm_root = vec![0u8; SPACE_MAP_ROOT_SIZE];
let mut cur = Cursor::new(&mut sm_root);
let r = write_metadata_sm(w)?;
r.pack(&mut cur)?;
let sm_root = pack_root(&r, SPACE_MAP_ROOT_SIZE)?;
Ok(sm_root)
}

View File

@ -134,7 +134,11 @@ fn unpack(data: &[u8]) -> IResult<&[u8], Superblock> {
pub fn read_superblock(engine: &dyn IoEngine, loc: u64) -> Result<Superblock> {
let b = engine.read(loc)?;
if let Ok((_, sb)) = unpack(&b.get_data()) {
if metadata_block_type(b.get_data()) != BT::CACHE_SUPERBLOCK {
return Err(anyhow!("bad checksum in superblock"));
}
if let Ok((_, sb)) = unpack(b.get_data()) {
Ok(sb)
} else {
Err(anyhow!("couldn't unpack superblock"))

View File

@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::{Arc, Mutex};
use threadpool::ThreadPool;
@ -565,6 +565,58 @@ pub fn btree_to_map_with_path<V: Unpack + Copy>(
//------------------------------------------
struct KeyCollector {
keys: Mutex<BTreeSet<u64>>,
}
impl KeyCollector {
fn new() -> KeyCollector {
KeyCollector {
keys: Mutex::new(BTreeSet::new()),
}
}
}
impl<V: Unpack + Copy> NodeVisitor<V> for KeyCollector {
fn visit(
&self,
_path: &[u64],
_kr: &KeyRange,
_h: &NodeHeader,
keys: &[u64],
_values: &[V],
) -> Result<()> {
let mut keyset = self.keys.lock().unwrap();
for k in keys {
keyset.insert(*k);
}
Ok(())
}
fn visit_again(&self, _path: &[u64], _b: u64) -> Result<()> {
Ok(())
}
fn end_walk(&self) -> Result<()> {
Ok(())
}
}
pub fn btree_to_key_set<V: Unpack + Copy>(
path: &mut Vec<u64>,
engine: Arc<dyn IoEngine + Send + Sync>,
ignore_non_fatal: bool,
root: u64,
) -> Result<BTreeSet<u64>> {
let walker = BTreeWalker::new(engine, ignore_non_fatal);
let visitor = KeyCollector::new();
walker.walk::<_, V>(path, &visitor, root)?;
Ok(visitor.keys.into_inner().unwrap())
}
//------------------------------------------
struct NoopVisitor<V> {
dummy: std::marker::PhantomData<V>,
}

View File

@ -195,6 +195,13 @@ impl Pack for SMRoot {
}
}
pub fn pack_root(root: &SMRoot, size: usize) -> Result<Vec<u8>> {
let mut sm_root = vec![0u8; size];
let mut cur = Cursor::new(&mut sm_root);
root.pack(&mut cur)?;
Ok(sm_root)
}
//------------------------------------------
pub fn write_common(w: &mut WriteBatcher, sm: &dyn SpaceMap) -> Result<(Vec<IndexEntry>, u64)> {

View File

@ -15,6 +15,7 @@ use crate::report::*;
use crate::thin::block_time::*;
use crate::thin::ir::{self, MetadataVisitor};
use crate::thin::metadata::*;
use crate::thin::metadata_repair::*;
use crate::thin::superblock::*;
use crate::thin::xml;
@ -147,6 +148,8 @@ pub struct ThinDumpOptions<'a> {
pub output: Option<&'a Path>,
pub async_io: bool,
pub report: Arc<Report>,
pub repair: bool,
pub overrides: SuperblockOverrides,
}
struct Context {
@ -311,7 +314,17 @@ pub fn dump_metadata(
pub fn dump(opts: ThinDumpOptions) -> Result<()> {
let ctx = mk_context(&opts)?;
let sb = read_superblock(ctx.engine.as_ref(), SUPERBLOCK_LOCATION)?;
let sb;
if opts.repair {
sb = read_or_rebuild_superblock(
ctx.engine.clone(),
ctx.report.clone(),
SUPERBLOCK_LOCATION,
&opts.overrides,
)?;
} else {
sb = read_superblock(ctx.engine.as_ref(), SUPERBLOCK_LOCATION)?;
}
let md = build_metadata(ctx.engine.clone(), &sb)?;
ctx.report

832
src/thin/metadata_repair.rs Normal file
View File

@ -0,0 +1,832 @@
use anyhow::{anyhow, Result};
use fixedbitset::FixedBitSet;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;
use crate::checksum;
use crate::io_engine::IoEngine;
use crate::pdata::btree::*;
use crate::pdata::btree_walker::*;
use crate::pdata::space_map_common::*;
use crate::pdata::unpack::Unpack;
use crate::report::Report;
use crate::thin::block_time::*;
use crate::thin::device_detail::*;
use crate::thin::superblock::*;
//------------------------------------------
pub struct SuperblockOverrides {
pub transaction_id: Option<u64>,
pub data_block_size: Option<u32>,
pub nr_data_blocks: Option<u64>,
}
pub struct FoundRoots {
mapping_root: u64,
details_root: u64,
time: u32,
transaction_id: u64,
nr_data_blocks: u64,
}
fn merge_time_counts(lhs: &mut BTreeMap<u32, u32>, rhs: &BTreeMap<u32, u32>) -> Result<()> {
for (t, c) in rhs.iter() {
*lhs.entry(*t).or_insert(0) += c;
}
Ok(())
}
struct DevInfo {
b: u64,
nr_devices: u64,
nr_mappings: u64,
key_low: u64, // min dev_id
key_high: u64, // max dev_id, inclusive
time_counts: BTreeMap<u32, u32>,
age: u32,
highest_mapped_data_block: u64,
pushed: bool,
}
impl DevInfo {
fn new(b: u64) -> DevInfo {
DevInfo {
b,
nr_devices: 0,
nr_mappings: 0,
key_low: 0,
key_high: 0,
time_counts: BTreeMap::<u32, u32>::new(),
age: 0,
highest_mapped_data_block: 0,
pushed: false,
}
}
fn push_child(&mut self, child: &DevInfo) -> Result<()> {
if self.key_high > 0 && child.key_low <= self.key_high {
return Err(anyhow!("incompatible child"));
}
if !self.pushed {
self.key_low = child.key_low;
self.pushed = true;
}
self.key_high = child.key_high;
self.nr_devices += child.nr_devices;
self.nr_mappings += child.nr_mappings;
merge_time_counts(&mut self.time_counts, &child.time_counts)?;
self.age = std::cmp::max(self.age, child.age);
self.highest_mapped_data_block = std::cmp::max(
self.highest_mapped_data_block,
child.highest_mapped_data_block,
);
Ok(())
}
}
struct MappingsInfo {
_b: u64,
nr_mappings: u64,
key_low: u64, // min mapped block
key_high: u64, // max mapped block, inclusive
time_counts: BTreeMap<u32, u32>,
age: u32,
highest_mapped_data_block: u64,
pushed: bool,
}
impl MappingsInfo {
fn new(b: u64) -> MappingsInfo {
MappingsInfo {
_b: b,
nr_mappings: 0,
key_low: 0,
key_high: 0,
time_counts: BTreeMap::<u32, u32>::new(),
age: 0,
highest_mapped_data_block: 0,
pushed: false,
}
}
fn push_child(&mut self, child: &MappingsInfo) -> Result<()> {
if self.key_high > 0 && child.key_low <= self.key_high {
return Err(anyhow!("incompatible child"));
}
if !self.pushed {
self.key_low = child.key_low;
self.pushed = true;
}
self.key_high = child.key_high;
self.nr_mappings += child.nr_mappings;
merge_time_counts(&mut self.time_counts, &child.time_counts)?;
self.age = std::cmp::max(self.age, child.age);
self.highest_mapped_data_block = std::cmp::max(
self.highest_mapped_data_block,
child.highest_mapped_data_block,
);
Ok(())
}
}
struct DetailsInfo {
_b: u64,
nr_devices: u64,
nr_mappings: u64,
key_low: u64, // min dev_id
key_high: u64, // max dev_id, inclusive
max_tid: u64,
age: u32,
pushed: bool,
}
impl DetailsInfo {
fn new(b: u64) -> DetailsInfo {
DetailsInfo {
_b: b,
nr_devices: 0,
nr_mappings: 0,
key_low: 0,
key_high: 0,
max_tid: 0,
age: 0,
pushed: false,
}
}
fn push_child(&mut self, child: &DetailsInfo) -> Result<()> {
if self.key_high > 0 && child.key_low <= self.key_high {
return Err(anyhow!("incompatible child"));
}
if !self.pushed {
self.key_low = child.key_low;
self.pushed = true;
}
self.key_high = child.key_high;
self.nr_devices += child.nr_devices;
self.nr_mappings += child.nr_mappings;
self.max_tid = std::cmp::max(self.max_tid, child.max_tid);
self.age = std::cmp::max(self.age, child.age);
Ok(())
}
}
enum NodeInfo {
Dev(DevInfo),
Mappings(MappingsInfo),
Details(DetailsInfo),
}
struct NodeCollector {
engine: Arc<dyn IoEngine + Send + Sync>,
nr_blocks: u64,
examined: FixedBitSet,
referenced: FixedBitSet,
infos: BTreeMap<u64, NodeInfo>,
report: Arc<Report>,
}
impl NodeCollector {
pub fn new(engine: Arc<dyn IoEngine + Send + Sync>, report: Arc<Report>) -> NodeCollector {
let nr_blocks = engine.get_nr_blocks();
NodeCollector {
engine,
nr_blocks,
examined: FixedBitSet::with_capacity(nr_blocks as usize),
referenced: FixedBitSet::with_capacity(nr_blocks as usize),
infos: BTreeMap::<u64, NodeInfo>::new(),
report,
}
}
fn gather_dev_subtree_info(
&mut self,
header: &NodeHeader,
_keys: &[u64],
values: &[u64],
) -> Result<NodeInfo> {
let mut info = DevInfo::new(header.block);
for b in values {
let child = self.get_info(*b)?;
if let NodeInfo::Dev(ref child) = child {
info.push_child(child)?;
} else {
return Err(anyhow!("incompatible child type"));
}
}
Ok(NodeInfo::Dev(info))
}
fn gather_mappings_subtree_info(
&mut self,
header: &NodeHeader,
_keys: &[u64],
values: &[u64],
) -> Result<NodeInfo> {
let mut info = MappingsInfo::new(header.block);
for b in values {
let child = self.get_info(*b)?;
if let NodeInfo::Mappings(ref child) = child {
info.push_child(child)?;
} else {
return Err(anyhow!("incompatible child type"));
}
}
Ok(NodeInfo::Mappings(info))
}
fn gather_details_subtree_info(
&mut self,
header: &NodeHeader,
_keys: &[u64],
values: &[u64],
) -> Result<NodeInfo> {
let mut info = DetailsInfo::new(header.block);
for b in values {
let child = self.get_info(*b)?;
if let NodeInfo::Details(ref child) = child {
info.push_child(child)?;
} else {
return Err(anyhow!("incompatible child type"));
}
}
Ok(NodeInfo::Details(info))
}
fn gather_subtree_info(
&mut self,
header: &NodeHeader,
keys: &[u64],
values: &[u64],
) -> Result<NodeInfo> {
// An internal node should have at least one entry
if header.nr_entries == 0 {
return Err(anyhow!("unexpected nr_entries"));
}
let first_child = self.get_info(values[0])?;
let info = match first_child {
NodeInfo::Dev { .. } => self.gather_dev_subtree_info(header, keys, values),
NodeInfo::Mappings { .. } => self.gather_mappings_subtree_info(header, keys, values),
NodeInfo::Details { .. } => self.gather_details_subtree_info(header, keys, values),
}?;
// the node is a valid parent, so mark the children as non-orphan
for b in values {
self.referenced.set(*b as usize, true);
}
Ok(info)
}
fn gather_dev_leaf_info(
&mut self,
header: &NodeHeader,
keys: &[u64],
values: &[u64],
) -> Result<NodeInfo> {
let mut info = DevInfo::new(header.block);
info.nr_devices = header.nr_entries as u64;
// min & max device id
if header.nr_entries > 0 {
info.key_low = keys[0];
info.key_high = keys[keys.len() - 1];
}
for b in values {
let child = self.get_info(*b)?; // subtree root
if let NodeInfo::Mappings(ref child) = child {
info.nr_mappings += child.nr_mappings;
merge_time_counts(&mut info.time_counts, &child.time_counts)?;
info.age = std::cmp::max(info.age, child.age);
info.highest_mapped_data_block = std::cmp::max(
info.highest_mapped_data_block,
child.highest_mapped_data_block,
);
} else {
return Err(anyhow!("not a data mapping subtree root"));
}
}
Ok(NodeInfo::Dev(info))
}
fn gather_mapping_leaf_info(
&self,
header: &NodeHeader,
keys: &[u64],
values: &[BlockTime],
) -> Result<NodeInfo> {
let mut info = MappingsInfo::new(header.block);
info.nr_mappings = header.nr_entries as u64;
// min & max logical block address
if header.nr_entries > 0 {
info.key_low = keys[0];
info.key_high = keys[keys.len() - 1];
}
for bt in values {
info.highest_mapped_data_block =
std::cmp::max(bt.block, info.highest_mapped_data_block);
*info.time_counts.entry(bt.time).or_insert(0) += 1;
info.age = std::cmp::max(info.age, bt.time);
}
Ok(NodeInfo::Mappings(info))
}
fn gather_details_leaf_info(
&self,
header: &NodeHeader,
keys: &[u64],
values: &[DeviceDetail],
) -> Result<NodeInfo> {
let mut info = DetailsInfo::new(header.block);
info.nr_devices = header.nr_entries as u64;
// min & max device id
if header.nr_entries > 0 {
info.key_low = keys[0];
info.key_high = keys[keys.len() - 1];
}
for details in values {
info.nr_mappings += details.mapped_blocks;
info.max_tid = std::cmp::max(info.max_tid, details.transaction_id);
info.age = std::cmp::max(info.age, details.creation_time);
info.age = std::cmp::max(info.age, details.snapshotted_time);
}
Ok(NodeInfo::Details(info))
}
fn is_top_level(&self, values: &[u64]) -> bool {
if values.is_empty() {
return false;
}
for v in values {
if *v > self.nr_blocks {
return false;
}
}
true
}
fn gather_info(&mut self, b: u64) -> Result<NodeInfo> {
let blk = self.engine.read(b)?;
let bt = checksum::metadata_block_type(blk.get_data());
if bt != checksum::BT::NODE {
return Err(anyhow!("not a btree node"));
}
let (_, hdr) = NodeHeader::unpack(blk.get_data())?;
if hdr.value_size as usize == std::mem::size_of::<u64>() {
let node = unpack_node::<u64>(&[0], blk.get_data(), true, true)?;
match node {
Node::Internal {
ref header,
ref keys,
ref values,
} => self.gather_subtree_info(header, keys, values),
Node::Leaf {
ref header,
ref keys,
ref values,
} => {
if self.is_top_level(values) {
self.gather_dev_leaf_info(header, keys, values)
} else {
// FIXME: convert the values only, to avoid unpacking the node twice
let node = unpack_node::<BlockTime>(&[0], blk.get_data(), true, true)?;
if let Node::Leaf {
ref header,
ref keys,
ref values,
} = node
{
self.gather_mapping_leaf_info(header, keys, values)
} else {
Err(anyhow!("unexpected internal node"))
}
}
}
}
} else if hdr.value_size == DeviceDetail::disk_size() {
let node = unpack_node::<DeviceDetail>(&[0], blk.get_data(), true, true)?;
if let Node::Leaf {
ref header,
ref keys,
ref values,
} = node
{
self.gather_details_leaf_info(header, keys, values)
} else {
Err(anyhow!("unexpected value size within an internal node"))
}
} else {
Err(anyhow!("not the value size of interest"))
}
}
fn read_info(&self, b: u64) -> Result<&NodeInfo> {
if self.examined.contains(b as usize) {
// TODO: use an extra 'valid' bitset for faster lookup?
self.infos
.get(&b)
.ok_or_else(|| anyhow!("block {} was examined as invalid", b))
} else {
Err(anyhow!("not examined"))
}
}
fn get_info(&mut self, b: u64) -> Result<&NodeInfo> {
if self.examined.contains(b as usize) {
// TODO: use an extra 'valid' bitset for faster lookup?
self.infos
.get(&b)
.ok_or_else(|| anyhow!("block {} was examined as invalid", b))
} else {
self.examined.set(b as usize, true);
let info = self.gather_info(b)?;
Ok(self.infos.entry(b).or_insert(info))
}
}
fn collect_infos(&mut self) -> Result<()> {
for b in 0..self.nr_blocks {
let _ret = self.get_info(b);
}
Ok(())
}
fn gather_roots(&self) -> Result<(Vec<u64>, Vec<u64>)> {
let mut dev_roots = Vec::<u64>::new();
let mut details_roots = Vec::<u64>::new();
for b in 0..self.nr_blocks {
// skip non-root blocks
if self.referenced.contains(b as usize) {
continue;
}
// skip blocks we're not interested in
let info = self.read_info(b as u64);
if info.is_err() {
continue;
}
let info = info.unwrap();
match info {
NodeInfo::Dev(_) => dev_roots.push(b),
NodeInfo::Details(_) => details_roots.push(b),
_ => {}
};
}
Ok((dev_roots, details_roots))
}
fn find_roots_with_compatible_ids(
&mut self,
dev_roots: &[u64],
details_roots: &[u64],
) -> Result<Vec<(u64, u64)>> {
let mut root_pairs = Vec::<(u64, u64)>::new();
for dev_root in dev_roots {
let mut path = vec![0];
let ids1 = btree_to_key_set::<u64>(&mut path, self.engine.clone(), true, *dev_root)?;
for details_root in details_roots {
let mut path = vec![0];
let ids2 = btree_to_key_set::<DeviceDetail>(
&mut path,
self.engine.clone(),
true,
*details_root,
)?;
if ids1 != ids2 {
continue;
}
root_pairs.push((*dev_root, *details_root));
}
}
Ok(root_pairs)
}
fn filter_roots_with_incompatible_mapped_blocks(
&self,
root_pairs: &[(u64, u64)],
) -> Result<Vec<(u64, u64)>> {
let mut filtered = Vec::<(u64, u64)>::new();
for (dev_root, details_root) in root_pairs {
let dev_info;
if let NodeInfo::Dev(i) = self.read_info(*dev_root)? {
dev_info = i;
} else {
continue;
}
let details_info;
if let NodeInfo::Details(i) = self.read_info(*details_root)? {
details_info = i;
} else {
continue;
}
// FIXME: compare the ages
if dev_info.nr_devices != details_info.nr_devices
|| dev_info.nr_mappings != details_info.nr_mappings
{
continue;
}
filtered.push((*dev_root, *details_root));
}
Ok(filtered)
}
fn compare_time_counts(lhs: &BTreeMap<u32, u32>, rhs: &BTreeMap<u32, u32>) -> Ordering {
let mut lhs_it = lhs.iter().rev();
let mut rhs_it = rhs.iter().rev();
let mut lhs_end = false;
let mut rhs_end = false;
while !lhs_end && !rhs_end {
if let Some((lhs_time, lhs_count)) = lhs_it.next() {
if let Some((rhs_time, rhs_count)) = rhs_it.next() {
if lhs_time > rhs_time {
return Ordering::Less;
} else if rhs_time > lhs_time {
return Ordering::Greater;
} else if lhs_count > rhs_count {
return Ordering::Less;
} else if rhs_count > lhs_count {
return Ordering::Greater;
}
} else {
rhs_end = true;
}
} else {
lhs_end = true;
}
}
if lhs_end && !rhs_end {
Ordering::Less
} else if !lhs_end && rhs_end {
Ordering::Greater
} else {
Ordering::Equal
}
}
fn compare_roots(lhs: &(&DevInfo, u64), rhs: &(&DevInfo, u64)) -> Ordering {
// TODO: sort by other criteria?
Self::compare_time_counts(&lhs.0.time_counts, &rhs.0.time_counts)
}
fn sort_roots(&self, root_pairs: &[(u64, u64)]) -> Result<Vec<(u64, u64)>> {
let mut infos = Vec::<(&DevInfo, u64)>::new();
for (dev_root, details_root) in root_pairs {
let dev_info;
if let NodeInfo::Dev(i) = self.read_info(*dev_root)? {
dev_info = i;
} else {
continue;
}
infos.push((dev_info, *details_root));
}
infos.sort_by(Self::compare_roots);
let mut sorted = Vec::<(u64, u64)>::new();
for (dev_info, details_root) in infos {
sorted.push((dev_info.b, details_root));
}
Ok(sorted)
}
fn find_root_pairs(
&mut self,
dev_roots: &[u64],
details_roots: &[u64],
) -> Result<Vec<(u64, u64)>> {
let pairs = self.find_roots_with_compatible_ids(dev_roots, details_roots)?;
let pairs = self.filter_roots_with_incompatible_mapped_blocks(&pairs)?;
self.sort_roots(&pairs)
}
fn to_found_roots(&self, dev_root: u64, details_root: u64) -> Result<FoundRoots> {
let dev_info;
if let NodeInfo::Dev(i) = self.read_info(dev_root)? {
dev_info = i;
} else {
return Err(anyhow!("not a top-level root"));
}
let details_info;
if let NodeInfo::Details(i) = self.read_info(details_root)? {
details_info = i;
} else {
return Err(anyhow!("not a details root"));
}
Ok(FoundRoots {
mapping_root: dev_root,
details_root,
time: std::cmp::max(dev_info.age, details_info.age),
transaction_id: details_info.max_tid + 1, // tid in superblock is ahead by 1
nr_data_blocks: dev_info.highest_mapped_data_block + 1,
})
}
fn log_results(&self, dev_roots: &[u64], details_roots: &[u64], pairs: &[(u64, u64)]) {
self.report
.info(&format!("mapping candidates ({}):", dev_roots.len()));
for dev_root in dev_roots {
if let Ok(NodeInfo::Dev(info)) = self.read_info(*dev_root) {
self.report.info(&format!("b={}, nr_devices={}, nr_mappings={}, highest_mapped={}, age={}, time_counts={:?}",
info.b, info.nr_devices, info.nr_mappings, info.highest_mapped_data_block, info.age, info.time_counts));
}
}
self.report
.info(&format!("\ndevice candidates ({}):", details_roots.len()));
for details_root in details_roots {
if let Ok(NodeInfo::Details(info)) = self.read_info(*details_root) {
self.report.info(&format!(
"b={}, nr_devices={}, nr_mappings={}, max_tid={}, age={}",
info._b, info.nr_devices, info.nr_mappings, info.max_tid, info.age
));
}
}
self.report
.info(&format!("\ncompatible roots ({}):", pairs.len()));
for pair in pairs {
self.report.info(&format!("({}, {})", pair.0, pair.1));
}
}
pub fn find_roots(mut self) -> Result<FoundRoots> {
self.collect_infos()?;
let (dev_roots, details_roots) = self.gather_roots()?;
let pairs = self.find_root_pairs(&dev_roots, &details_roots)?;
self.log_results(&dev_roots, &details_roots, &pairs);
if pairs.is_empty() {
return Err(anyhow!("no compatible roots found"));
}
self.to_found_roots(pairs[0].0, pairs[0].1)
}
}
fn check_data_block_size(bs: u32) -> Result<u32> {
if !(128..=2097152).contains(&bs) || (bs & 0x7F != 0) {
return Err(anyhow!("invalid data block size"));
}
Ok(bs)
}
//------------------------------------------
#[derive(Debug)]
struct SuperblockError {
failed_sb: Option<Superblock>,
}
impl fmt::Display for SuperblockError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.failed_sb)
}
}
impl std::error::Error for SuperblockError {}
pub fn is_superblock_consistent(
sb: Superblock,
engine: Arc<dyn IoEngine + Send + Sync>,
) -> Result<Superblock> {
let mut path = vec![0];
let ids1 = btree_to_key_set::<u64>(&mut path, engine.clone(), true, sb.mapping_root);
path = vec![0];
let ids2 = btree_to_key_set::<DeviceDetail>(&mut path, engine.clone(), true, sb.details_root);
if ids1.is_err() || ids2.is_err() || ids1.unwrap() != ids2.unwrap() {
return Err(anyhow::Error::new(SuperblockError {
failed_sb: Some(sb),
})
.context("inconsistent device ids"));
}
Ok(sb)
}
pub fn rebuild_superblock(
engine: Arc<dyn IoEngine + Send + Sync>,
report: Arc<Report>,
ref_sb: Option<Superblock>,
opts: &SuperblockOverrides,
) -> Result<Superblock> {
// 1. Takes the user overrides
// 2. Takes the reference if there's no user overrides
// 3. Returns Err if none of the values above are present
// 4. Validates the taken value
let data_block_size = opts
.data_block_size
.or_else(|| ref_sb.as_ref().map(|sb| sb.data_block_size))
.ok_or_else(|| {
anyhow!("data block size needs to be provided due to corruption in the superblock")
})
.and_then(check_data_block_size)?;
let c = NodeCollector::new(engine.clone(), report);
let roots = c.find_roots()?;
let transaction_id = opts
.transaction_id
.or_else(|| ref_sb.as_ref().map(|sb| sb.transaction_id))
.filter(|tid| *tid > roots.transaction_id)
.unwrap_or(roots.transaction_id);
let nr_data_blocks = opts
.nr_data_blocks
.or_else(|| {
ref_sb.as_ref().and_then(|sb| {
unpack_root(&sb.data_sm_root)
.ok()
.map(|root| root.nr_blocks)
})
})
.filter(|n| *n > roots.nr_data_blocks)
.unwrap_or(roots.nr_data_blocks);
let sm_root = SMRoot {
nr_blocks: nr_data_blocks,
nr_allocated: 0,
bitmap_root: 0,
ref_count_root: 0,
};
let data_sm_root = pack_root(&sm_root, SPACE_MAP_ROOT_SIZE)?;
Ok(Superblock {
flags: SuperblockFlags { needs_check: false },
block: SUPERBLOCK_LOCATION,
version: 2,
time: roots.time,
transaction_id,
metadata_snap: 0,
data_sm_root,
metadata_sm_root: vec![0u8; SPACE_MAP_ROOT_SIZE],
mapping_root: roots.mapping_root,
details_root: roots.details_root,
data_block_size,
nr_metadata_blocks: 0,
})
}
pub fn read_or_rebuild_superblock(
engine: Arc<dyn IoEngine + Send + Sync>,
report: Arc<Report>,
loc: u64,
opts: &SuperblockOverrides,
) -> Result<Superblock> {
read_superblock(engine.as_ref(), loc)
.and_then(|sb| is_superblock_consistent(sb, engine.clone()))
.or_else(|e| {
let ref_sb = e
.downcast_ref::<SuperblockError>()
.and_then(|err| err.failed_sb.clone());
rebuild_superblock(engine, report, ref_sb, opts)
})
}
//------------------------------------------

View File

@ -4,6 +4,7 @@ pub mod device_detail;
pub mod dump;
pub mod ir;
pub mod metadata;
pub mod metadata_repair;
pub mod repair;
pub mod restore;
pub mod runs;

View File

@ -7,6 +7,7 @@ use crate::pdata::space_map_metadata::*;
use crate::report::*;
use crate::thin::dump::*;
use crate::thin::metadata::*;
use crate::thin::metadata_repair::*;
use crate::thin::restore::*;
use crate::thin::superblock::*;
use crate::write_batcher::*;
@ -18,6 +19,7 @@ pub struct ThinRepairOptions<'a> {
pub output: &'a Path,
pub async_io: bool,
pub report: Arc<Report>,
pub overrides: SuperblockOverrides,
}
struct Context {
@ -53,7 +55,12 @@ fn new_context(opts: &ThinRepairOptions) -> Result<Context> {
pub fn repair(opts: ThinRepairOptions) -> Result<()> {
let ctx = new_context(&opts)?;
let sb = read_superblock(ctx.engine_in.as_ref(), SUPERBLOCK_LOCATION)?;
let sb = read_or_rebuild_superblock(
ctx.engine_in.clone(),
ctx.report.clone(),
SUPERBLOCK_LOCATION,
&opts.overrides,
)?;
let md = build_metadata(ctx.engine_in.clone(), &sb)?;
let md = optimise_metadata(md)?;

View File

@ -2,7 +2,6 @@ use anyhow::{anyhow, Result};
use std::collections::BTreeMap;
use std::fs::OpenOptions;
use std::io::Cursor;
use std::ops::Deref;
use std::path::Path;
use std::sync::{Arc, Mutex};
@ -10,10 +9,9 @@ use std::sync::{Arc, Mutex};
use crate::io_engine::*;
use crate::pdata::btree_builder::*;
use crate::pdata::space_map::*;
use crate::pdata::space_map_common::SMRoot;
use crate::pdata::space_map_common::pack_root;
use crate::pdata::space_map_disk::*;
use crate::pdata::space_map_metadata::*;
use crate::pdata::unpack::Pack;
use crate::report::*;
use crate::thin::block_time::*;
use crate::thin::device_detail::*;
@ -160,7 +158,8 @@ impl<'a> Restorer<'a> {
let data_sm_root = build_data_sm(self.w, data_sm.lock().unwrap().deref())?;
// Build metadata space map
let (metadata_sm, metadata_sm_root) = build_metadata_sm(self.w)?;
let metadata_sm = write_metadata_sm(self.w)?;
let metadata_sm_root = pack_root(&metadata_sm, SPACE_MAP_ROOT_SIZE)?;
// Write the superblock
let sb = self.sb.as_ref().unwrap();
@ -289,25 +288,12 @@ impl<'a> MetadataVisitor for Restorer<'a> {
/// Writes a data space map to disk. Returns the space map root that needs
/// to be written to the superblock.
fn build_data_sm(w: &mut WriteBatcher, sm: &dyn SpaceMap) -> Result<Vec<u8>> {
let mut sm_root = vec![0u8; SPACE_MAP_ROOT_SIZE];
let mut cur = Cursor::new(&mut sm_root);
let r = write_disk_sm(w, sm)?;
r.pack(&mut cur)?;
let sm_root = pack_root(&r, SPACE_MAP_ROOT_SIZE)?;
Ok(sm_root)
}
/// Writes the metadata space map to disk. Returns the space map root that needs
/// to be written to the superblock.
fn build_metadata_sm(w: &mut WriteBatcher) -> Result<(SMRoot, Vec<u8>)> {
let mut sm_root = vec![0u8; SPACE_MAP_ROOT_SIZE];
let mut cur = Cursor::new(&mut sm_root);
let r = write_metadata_sm(w)?;
r.pack(&mut cur)?;
Ok((r, sm_root))
}
//------------------------------------------
pub struct ThinRestoreOptions<'a> {

View File

@ -89,7 +89,11 @@ fn unpack(data: &[u8]) -> IResult<&[u8], Superblock> {
pub fn read_superblock(engine: &dyn IoEngine, loc: u64) -> Result<Superblock> {
let b = engine.read(loc)?;
if let Ok((_, sb)) = unpack(&b.get_data()) {
if metadata_block_type(b.get_data()) != BT::THIN_SUPERBLOCK {
return Err(anyhow!("bad checksum in superblock"));
}
if let Ok((_, sb)) = unpack(b.get_data()) {
Ok(sb)
} else {
Err(anyhow!("couldn't unpack superblock"))