[thin_repair (rust)] Support setting missing superblock fields automatically
While rebuilding a superblock, the transaction_id, data_block_size, and nr_data_blocks could be inherited from the current superblock if available (assumed it's a multiple-activated copy, so supposed partially valid), or derived from the probed data mappings and device details, that saves the hassle of manual input. Note that either the current superblock or user-overrides must be compatible with the probed values, thus the filters are applied.
This commit is contained in:
parent
5dad1097c3
commit
434d24d10a
@ -2,6 +2,7 @@ use anyhow::{anyhow, Result};
|
|||||||
use fixedbitset::FixedBitSet;
|
use fixedbitset::FixedBitSet;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::checksum;
|
use crate::checksum;
|
||||||
@ -26,6 +27,8 @@ pub struct FoundRoots {
|
|||||||
mapping_root: u64,
|
mapping_root: u64,
|
||||||
details_root: u64,
|
details_root: u64,
|
||||||
time: u32,
|
time: u32,
|
||||||
|
transaction_id: u64,
|
||||||
|
nr_data_blocks: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_time_counts(lhs: &mut BTreeMap<u32, u32>, rhs: &BTreeMap<u32, u32>) -> Result<()> {
|
fn merge_time_counts(lhs: &mut BTreeMap<u32, u32>, rhs: &BTreeMap<u32, u32>) -> Result<()> {
|
||||||
@ -43,6 +46,7 @@ struct DevInfo {
|
|||||||
key_high: u64, // max dev_id, inclusive
|
key_high: u64, // max dev_id, inclusive
|
||||||
time_counts: BTreeMap<u32, u32>,
|
time_counts: BTreeMap<u32, u32>,
|
||||||
age: u32,
|
age: u32,
|
||||||
|
highest_mapped_data_block: u64,
|
||||||
pushed: bool,
|
pushed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +60,7 @@ impl DevInfo {
|
|||||||
key_high: 0,
|
key_high: 0,
|
||||||
time_counts: BTreeMap::<u32, u32>::new(),
|
time_counts: BTreeMap::<u32, u32>::new(),
|
||||||
age: 0,
|
age: 0,
|
||||||
|
highest_mapped_data_block: 0,
|
||||||
pushed: false,
|
pushed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,6 +78,10 @@ impl DevInfo {
|
|||||||
self.nr_mappings += child.nr_mappings;
|
self.nr_mappings += child.nr_mappings;
|
||||||
merge_time_counts(&mut self.time_counts, &child.time_counts)?;
|
merge_time_counts(&mut self.time_counts, &child.time_counts)?;
|
||||||
self.age = std::cmp::max(self.age, child.age);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -85,6 +94,7 @@ struct MappingsInfo {
|
|||||||
key_high: u64, // max mapped block, inclusive
|
key_high: u64, // max mapped block, inclusive
|
||||||
time_counts: BTreeMap<u32, u32>,
|
time_counts: BTreeMap<u32, u32>,
|
||||||
age: u32,
|
age: u32,
|
||||||
|
highest_mapped_data_block: u64,
|
||||||
pushed: bool,
|
pushed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +107,7 @@ impl MappingsInfo {
|
|||||||
key_high: 0,
|
key_high: 0,
|
||||||
time_counts: BTreeMap::<u32, u32>::new(),
|
time_counts: BTreeMap::<u32, u32>::new(),
|
||||||
age: 0,
|
age: 0,
|
||||||
|
highest_mapped_data_block: 0,
|
||||||
pushed: false,
|
pushed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,6 +124,10 @@ impl MappingsInfo {
|
|||||||
self.nr_mappings += child.nr_mappings;
|
self.nr_mappings += child.nr_mappings;
|
||||||
merge_time_counts(&mut self.time_counts, &child.time_counts)?;
|
merge_time_counts(&mut self.time_counts, &child.time_counts)?;
|
||||||
self.age = std::cmp::max(self.age, child.age);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -122,8 +137,9 @@ struct DetailsInfo {
|
|||||||
_b: u64,
|
_b: u64,
|
||||||
nr_devices: u64,
|
nr_devices: u64,
|
||||||
nr_mappings: u64,
|
nr_mappings: u64,
|
||||||
key_low: u64,
|
key_low: u64, // min dev_id
|
||||||
key_high: u64, // inclusive
|
key_high: u64, // max dev_id, inclusive
|
||||||
|
max_tid: u64,
|
||||||
age: u32,
|
age: u32,
|
||||||
pushed: bool,
|
pushed: bool,
|
||||||
}
|
}
|
||||||
@ -136,6 +152,7 @@ impl DetailsInfo {
|
|||||||
nr_mappings: 0,
|
nr_mappings: 0,
|
||||||
key_low: 0,
|
key_low: 0,
|
||||||
key_high: 0,
|
key_high: 0,
|
||||||
|
max_tid: 0,
|
||||||
age: 0,
|
age: 0,
|
||||||
pushed: false,
|
pushed: false,
|
||||||
}
|
}
|
||||||
@ -152,6 +169,7 @@ impl DetailsInfo {
|
|||||||
self.key_high = child.key_high;
|
self.key_high = child.key_high;
|
||||||
self.nr_devices += child.nr_devices;
|
self.nr_devices += child.nr_devices;
|
||||||
self.nr_mappings += child.nr_mappings;
|
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);
|
self.age = std::cmp::max(self.age, child.age);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -291,6 +309,10 @@ impl NodeCollector {
|
|||||||
info.nr_mappings += child.nr_mappings;
|
info.nr_mappings += child.nr_mappings;
|
||||||
merge_time_counts(&mut info.time_counts, &child.time_counts)?;
|
merge_time_counts(&mut info.time_counts, &child.time_counts)?;
|
||||||
info.age = std::cmp::max(info.age, child.age);
|
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 {
|
} else {
|
||||||
return Err(anyhow!("not a data mapping subtree root"));
|
return Err(anyhow!("not a data mapping subtree root"));
|
||||||
}
|
}
|
||||||
@ -315,12 +337,12 @@ impl NodeCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for bt in values {
|
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.time_counts.entry(bt.time).or_insert(0) += 1;
|
||||||
info.age = std::cmp::max(info.age, bt.time);
|
info.age = std::cmp::max(info.age, bt.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: propagate the low & high data block address upward,
|
|
||||||
// for rebuilding the nr_data_blocks in superblock?
|
|
||||||
Ok(NodeInfo::Mappings(info))
|
Ok(NodeInfo::Mappings(info))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,6 +363,7 @@ impl NodeCollector {
|
|||||||
|
|
||||||
for details in values {
|
for details in values {
|
||||||
info.nr_mappings += details.mapped_blocks;
|
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.creation_time);
|
||||||
info.age = std::cmp::max(info.age, details.snapshotted_time);
|
info.age = std::cmp::max(info.age, details.snapshotted_time);
|
||||||
}
|
}
|
||||||
@ -635,6 +658,8 @@ impl NodeCollector {
|
|||||||
mapping_root: dev_root,
|
mapping_root: dev_root,
|
||||||
details_root,
|
details_root,
|
||||||
time: std::cmp::max(dev_info.age, details_info.age),
|
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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,20 +676,43 @@ impl NodeCollector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
pub fn is_superblock_consistent(
|
||||||
sb: Superblock,
|
sb: Superblock,
|
||||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||||
) -> Result<Superblock> {
|
) -> Result<Superblock> {
|
||||||
let mut path = vec![0];
|
let mut path = vec![0];
|
||||||
let ids1 = btree_to_key_set::<u64>(&mut path, engine.clone(), true, sb.mapping_root)?;
|
let ids1 = btree_to_key_set::<u64>(&mut path, engine.clone(), true, sb.mapping_root);
|
||||||
|
|
||||||
path = vec![0];
|
path = vec![0];
|
||||||
let ids2 = btree_to_key_set::<DeviceDetail>(&mut path, engine.clone(), true, sb.details_root)?;
|
let ids2 = btree_to_key_set::<DeviceDetail>(&mut path, engine.clone(), true, sb.details_root);
|
||||||
|
|
||||||
if ids1 != ids2 {
|
if ids1.is_err() || ids2.is_err() || ids1.unwrap() != ids2.unwrap() {
|
||||||
return Err(anyhow!("inconsistent device ids"));
|
return Err(anyhow::Error::new(SuperblockError {
|
||||||
|
failed_sb: Some(sb),
|
||||||
|
})
|
||||||
|
.context("inconsistent device ids"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(sb)
|
Ok(sb)
|
||||||
@ -672,15 +720,42 @@ pub fn is_superblock_consistent(
|
|||||||
|
|
||||||
pub fn rebuild_superblock(
|
pub fn rebuild_superblock(
|
||||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||||
|
ref_sb: Option<Superblock>,
|
||||||
opts: &SuperblockOverrides,
|
opts: &SuperblockOverrides,
|
||||||
) -> Result<Superblock> {
|
) -> Result<Superblock> {
|
||||||
// Check parameters
|
// 1. Takes the user overrides
|
||||||
let nr_data_blocks = opts.nr_data_blocks.ok_or_else(|| anyhow!(""))?;
|
// 2. Takes the reference if there's no user overrides
|
||||||
let transaction_id = opts.transaction_id.ok_or_else(|| anyhow!(""))?;
|
// 3. Returns Err if none of the values above are present
|
||||||
let data_block_size = opts.data_block_size.ok_or_else(|| anyhow!(""))?;
|
// 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());
|
let c = NodeCollector::new(engine.clone());
|
||||||
let roots = c.find_roots()?;
|
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 {
|
let sm_root = SMRoot {
|
||||||
nr_blocks: nr_data_blocks,
|
nr_blocks: nr_data_blocks,
|
||||||
nr_allocated: 0,
|
nr_allocated: 0,
|
||||||
@ -712,7 +787,12 @@ pub fn read_or_rebuild_superblock(
|
|||||||
) -> Result<Superblock> {
|
) -> Result<Superblock> {
|
||||||
read_superblock(engine.as_ref(), loc)
|
read_superblock(engine.as_ref(), loc)
|
||||||
.and_then(|sb| is_superblock_consistent(sb, engine.clone()))
|
.and_then(|sb| is_superblock_consistent(sb, engine.clone()))
|
||||||
.or_else(|_| rebuild_superblock(engine, &opts))
|
.or_else(|e| {
|
||||||
|
let ref_sb = e
|
||||||
|
.downcast_ref::<SuperblockError>()
|
||||||
|
.and_then(|err| err.failed_sb.clone());
|
||||||
|
rebuild_superblock(engine, ref_sb, opts)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user