From 04e0eb3a66550ff04d24edb80a0574a0651144d1 Mon Sep 17 00:00:00 2001 From: Joe Thornber Date: Wed, 9 Dec 2020 13:22:32 +0000 Subject: [PATCH 01/11] [thin_restore (rust)] rewrite the btree_builder Now copes with adding shared leaves. --- src/pdata/btree_builder.rs | 584 ++++++++++++++++++++++++------------- src/pdata/space_map.rs | 17 +- src/thin/restore.rs | 65 ++++- src/write_batcher.rs | 5 +- 4 files changed, 465 insertions(+), 206 deletions(-) diff --git a/src/pdata/btree_builder.rs b/src/pdata/btree_builder.rs index 05567cd..4403cc7 100644 --- a/src/pdata/btree_builder.rs +++ b/src/pdata/btree_builder.rs @@ -13,6 +13,56 @@ use crate::write_batcher::*; //------------------------------------------ +/// A little ref counter abstraction. Used to manage counts for btree +/// values (eg, the block/time in a thin mapping tree). +pub trait RefCounter { + fn get(&self, v: &Value) -> Result; + fn inc(&mut self, v: &Value) -> Result<()>; + fn dec(&mut self, v: &Value) -> Result<()>; +} + +/// Wraps a space map up to become a RefCounter. +struct SMRefCounter { + sm: Arc>, +} + +impl RefCounter for SMRefCounter { + fn get(&self, v: &u64) -> Result { + self.sm.lock().unwrap().get(*v) + } + + fn inc(&mut self, v: &u64) -> Result<()> { + self.sm.lock().unwrap().inc(*v, 1) + } + + fn dec(&mut self, v: &u64) -> Result<()> { + self.sm.lock().unwrap().dec(*v)?; + Ok(()) + } +} + +//------------------------------------------ + +// Building a btree for a given set of values is straight forward. +// But often we want to merge shared subtrees into the btree we're +// building, which _is_ complicated. Requiring rebalancing of nodes, +// and careful copy-on-write operations so we don't disturb the shared +// subtree. +// +// To avoid these problems this code never produces shared internal nodes. +// With the large fan out of btrees this isn't really a problem; we'll +// allocate more nodes than optimum, but not many compared to the number +// of leaves. Also we can pack the leaves much better than the kernel +// does due to out of order insertions. +// +// There are thus two stages to building a btree. +// +// i) Produce a list of populated leaves. These leaves may well be shared. +// ii) Build the upper levels of the btree above the leaves. + +//------------------------------------------ + +/// Pack the given node ready to write to disk. fn pack_node(node: &Node, w: &mut W) -> Result<()> { match node { Node::Internal { @@ -58,100 +108,13 @@ fn pack_node(node: &Node, w: &mut W) -> R Ok(()) } -//------------------------------------------ - -fn calc_max_entries() -> usize { - let elt_size = 8 + V::disk_size() as usize; - ((BLOCK_SIZE - NodeHeader::disk_size() as usize) / elt_size) as usize +pub struct WriteResult { + first_key: u64, + loc: u64, } -//------------------------------------------ - -struct Entries { - pub max_entries: usize, - entries: VecDeque<(u64, V)>, -} - -enum Action { - EmitNode(Vec, Vec), // keys, values -} - -use Action::*; - -impl Entries { - pub fn new(max_entries: usize) -> Entries { - Entries { - max_entries, - entries: VecDeque::new(), - } - } - - pub fn add_entry(&mut self, k: u64, v: V) -> Vec> { - let mut result = Vec::new(); - - if self.full() { - let (keys, values) = self.pop(self.max_entries); - result.push(EmitNode(keys, values)); - } - - self.entries.push_back((k, v)); - result - } - - fn complete_(&mut self, result: &mut Vec>) { - let n = self.entries.len(); - - if n >= self.max_entries { - let n1 = n / 2; - let n2 = n - n1; - let (keys1, values1) = self.pop(n1); - let (keys2, values2) = self.pop(n2); - - result.push(EmitNode(keys1, values1)); - result.push(EmitNode(keys2, values2)); - } else if n > 0 { - let (keys, values) = self.pop(n); - result.push(EmitNode(keys, values)); - } - } - - pub fn complete(&mut self) -> Vec> { - let mut result = Vec::new(); - self.complete_(&mut result); - result - } - - fn full(&self) -> bool { - self.entries.len() >= 2 * self.max_entries - } - - fn pop(&mut self, count: usize) -> (Vec, Vec) { - let mut keys = Vec::new(); - let mut values = Vec::new(); - - for _i in 0..count { - let (k, v) = self.entries.pop_front().unwrap(); - keys.push(k); - values.push(v); - } - - (keys, values) - } -} - -//------------------------------------------ - -#[allow(dead_code)] -pub struct NodeSummary { - block: u64, - nr_entries: usize, - key_low: u64, - key_high: u64, // inclusive -} - -//------------------------------------------ - -fn write_node_(w: &mut WriteBatcher, mut node: Node) -> Result<(u64, u64)> { +/// Write a node to a free metadata block. +fn write_node_(w: &mut WriteBatcher, mut node: Node) -> Result { let keys = node.get_keys(); let first_key = keys.first().unwrap_or(&0u64).clone(); @@ -163,155 +126,380 @@ fn write_node_(w: &mut WriteBatcher, mut node: Node) -> Res pack_node(&node, &mut cursor)?; w.write(b, checksum::BT::NODE)?; - Ok((first_key, loc)) + Ok(WriteResult { first_key, loc }) } -fn write_leaf( - w: &mut WriteBatcher, - keys: Vec, - values: Vec, -) -> Result<(u64, u64)> { - let header = NodeHeader { - block: 0, - is_leaf: true, - nr_entries: keys.len() as u32, - max_entries: calc_max_entries::() as u32, - value_size: V::disk_size(), - }; - - let node = Node::Leaf { - header, - keys, - values, - }; - - write_node_(w, node) +/// A node writer takes a Vec of values and packs them into +/// a btree node. It's up to the specific implementation to +/// decide if it produces internal or leaf nodes. +pub trait NodeIO { + fn write(&self, w: &mut WriteBatcher, keys: Vec, values: Vec) -> Result; + fn read( + &self, + engine: &Arc, + block: u64, + ) -> Result<(Vec, Vec)>; } -fn write_internal(w: &mut WriteBatcher, keys: Vec, values: Vec) -> Result<(u64, u64)> { - let header = NodeHeader { - block: 0, - is_leaf: false, - nr_entries: keys.len() as u32, - max_entries: calc_max_entries::() as u32, - value_size: u64::disk_size(), - }; +struct LeafIO {} - let node: Node = Node::Internal { - header, - keys, - values, - }; +impl NodeIO for LeafIO { + fn write(&self, w: &mut WriteBatcher, keys: Vec, values: Vec) -> Result { + let header = NodeHeader { + block: 0, + is_leaf: true, + nr_entries: keys.len() as u32, + max_entries: calc_max_entries::() as u32, + value_size: V::disk_size(), + }; - write_node_(w, node) + let node = Node::Leaf { + header, + keys, + values, + }; + + write_node_(w, node) + } + + fn read( + &self, + engine: &Arc, + block: u64, + ) -> Result<(Vec, Vec)> { + let b = engine.read(block)?; + let path = Vec::new(); + match unpack_node::(&path, b.get_data(), true, true)? { + Node::Internal { .. } => { + panic!("unexpected internal node"); + } + Node::Leaf { keys, values, .. } => Ok((keys, values)), + } + } } -pub struct Builder { - w: WriteBatcher, - entries: Entries, +struct InternalIO {} - max_internal_entries: usize, - internal_entries: Vec>, +impl NodeIO for InternalIO { + fn write(&self, w: &mut WriteBatcher, keys: Vec, values: Vec) -> Result { + let header = NodeHeader { + block: 0, + is_leaf: false, + nr_entries: keys.len() as u32, + max_entries: calc_max_entries::() as u32, + value_size: u64::disk_size(), + }; - root: u64, + let node: Node = Node::Internal { + header, + keys, + values, + }; + + write_node_(w, node) + } + + fn read( + &self, + engine: &Arc, + block: u64, + ) -> Result<(Vec, Vec)> { + let b = engine.read(block)?; + let path = Vec::new(); + match unpack_node::(&path, b.get_data(), true, true)? { + Node::Internal { keys, values, .. } => Ok((keys, values)), + Node::Leaf { .. } => { + panic!("unexpected leaf node"); + } + } + } } -impl Builder { +//------------------------------------------ + +/// What is the maximum number of entries of a given size we can fit in +/// a btree node? +fn calc_max_entries() -> usize { + let elt_size = 8 + V::disk_size() as usize; + ((BLOCK_SIZE - NodeHeader::disk_size() as usize) / elt_size) as usize +} + +//------------------------------------------ + +/// This takes a sequence of values or nodes, and builds a vector of leaf nodes. +/// Care is taken to make sure that all nodes are at least half full unless there's +/// only a single node. +pub struct NodeBuilder { + batcher: WriteBatcher, + nio: Box>, + value_rc: Box>, + max_entries_per_node: usize, + values: VecDeque<(u64, V)>, + nodes: Vec, +} + +/// When the builder is including pre-built nodes it has to decide whether +/// to use the node as given, or read it and import the values directly +/// for balancing reasons. This struct is used to stop us re-reading +/// the NodeHeaders of nodes that are shared multiple times. +#[derive(Clone)] +pub struct NodeSummary { + block: u64, + key: u64, + nr_entries: usize, + + /// This node was passed in pre-built. Important for deciding if + /// we need to adjust the ref counts if we unpack. + shared: bool, +} + +impl NodeBuilder { + /// Create a new NodeBuilder pub fn new( - engine: Arc, - sm: Arc>, - ) -> Builder { - let max_entries = calc_max_entries::(); - let max_internal_entries = calc_max_entries::(); - - Builder { - w: WriteBatcher::new(engine, sm, 256), - entries: Entries::new(max_entries), - max_internal_entries, - internal_entries: Vec::new(), - root: 0, + batcher: WriteBatcher, + nio: Box>, + value_rc: Box>, + ) -> Self { + NodeBuilder { + batcher, + nio, + value_rc, + max_entries_per_node: calc_max_entries::(), + values: VecDeque::new(), + nodes: Vec::new(), } } - pub fn add_entry(&mut self, k: u64, v: V) -> Result<()> { - let actions = self.entries.add_entry(k, v); - for a in actions { - self.perform_action(a)?; + /// Push a single value. This may emit a new node, hence the Result + /// return type. The value's ref count will be incremented. + pub fn push_value(&mut self, key: u64, val: V) -> Result<()> { + // Have we got enough values to emit a node? We try and keep + // at least max_entries_per_node entries unflushed so we + // can ensure the final node is balanced properly. + if self.values.len() == self.max_entries_per_node * 2 { + self.emit_node()?; } + self.value_rc.inc(&val)?; + self.values.push_back((key, val)); Ok(()) } - pub fn add_leaf_node(&mut self, leaf: &NodeSummary) -> Result<()> { - match leaf.nr_entries { - n if n == 0 => { - // Do nothing - }, - n if n < (self.entries.max_entries / 2) => { - // FIXME: what if we've already queued a handful of entries for a node? - // Add the entries individually - todo!(); - }, - _n => { - let actions = self.entries.complete(); - for a in actions { - self.perform_action(a)?; - } - self.add_internal_entry(0, leaf.key_low, leaf.block)?; + /// Push a number of prebuilt, shared nodes. The builder may decide to not + /// use a shared node, instead reading the values and packing them + /// directly. This may do IO to emit nodes, so returns a Result. + /// 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, nodes: &Vec) -> Result<()> { + assert!(nodes.len() > 0); + + // As a sanity check we make sure that all the shared nodes contain the + // minimum nr of entries. + let half_full = self.max_entries_per_node / 2; + for n in nodes { + if n.nr_entries < half_full { + panic!("under populated node"); + } + } + + // Decide if we're going to use the pre-built nodes. + if 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(nodes.get(0).unwrap().block)?; + + for i in 0..keys.len() { + self.value_rc.inc(&values[i])?; + self.values.push_back((keys[i], values[i].clone())); + } + + // Flush all the values. + self.emit_all()?; + + // Add the remaining nodes. + for i in 1..nodes.len() { + let n = nodes.get(i).unwrap(); + self.batcher.sm.lock().unwrap().inc(n.block, 1)?; + self.nodes.push(n.clone()); + } + } else { + // Flush all the values. + self.emit_all()?; + + // add the nodes + for n in nodes { + self.batcher.sm.lock().unwrap().inc(n.block, 1)?; + self.nodes.push(n.clone()); } } Ok(()) } - pub fn complete(mut self) -> Result { - let actions = self.entries.complete(); - for a in actions { - self.perform_action(a)?; + /// Signal that no more values or nodes will be pushed. Returns a + /// vector of the built nodes. Consumes the builder. + pub fn complete(mut self) -> Result> { + let half_full = self.max_entries_per_node / 2; + + if (self.nodes.len() > 0) && (self.values.len() < half_full) { + // 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()?; } - self.w.flush()?; - Ok(self.root) + + self.emit_all()?; + Ok(self.nodes) } - //-------------------- + //------------------------- - fn add_internal_entry(&mut self, level: usize, k: u64, v: u64) -> Result<()> { - if self.internal_entries.len() == level { - self.internal_entries - .push(Entries::new(self.max_internal_entries)); + // We're only interested in the keys and values from the node, and + // not whether it's a leaf or internal node. + fn read_node(&self, block: u64) -> Result<(Vec, Vec)> { + self.nio.read(&self.batcher.engine, block) + } + + /// Writes a node with the first 'nr_entries' values. + fn emit_values(&mut self, nr_entries: usize) -> Result<()> { + assert!(self.values.len() <= nr_entries); + + // Write the node + let mut keys = Vec::new(); + let mut values = Vec::new(); + + for _i in 0..nr_entries { + let (k, v) = self.values.pop_front().unwrap(); + keys.push(k); + values.push(v); } - let actions = self.internal_entries[level].add_entry(k, v); - - for a in actions { - self.perform_internal_action(level, a)?; - } + let wresult = self.nio.write(&mut self.batcher, keys, values)?; + // Push a summary to the 'nodes' vector. + self.nodes.push(NodeSummary { + block: wresult.loc, + key: wresult.first_key, + nr_entries, + shared: false, + }); Ok(()) } - fn perform_internal_action(&mut self, level: usize, action: Action) -> Result<()> { - match action { - EmitNode(keys, values) => { - let (k, loc) = write_internal(&mut self.w, keys, values)?; - self.add_internal_entry(level + 1, k, loc)?; - self.root = loc; - }, - } - - Ok(()) + /// Writes a full node. + fn emit_node(&mut self) -> Result<()> { + self.emit_values(self.max_entries_per_node) } - fn perform_action(&mut self, action: Action) -> Result<()> { - match action { - EmitNode(keys, values) => { - let (k, loc) = write_leaf(&mut self.w, keys, values)?; - self.add_internal_entry(0, k, loc)?; - }, + /// Emits all remaining values. Panics if there are more than 2 * + /// max_entries_per_node values. + fn emit_all(&mut self) -> Result<()> { + match self.values.len() { + 0 => { + // There's nothing to emit + Ok(()) + } + n if n <= self.max_entries_per_node => { + // Emit a single node. + self.emit_values(n) + } + n if n <= self.max_entries_per_node * 2 => { + // Emit two nodes. + let n1 = n / 2; + let n2 = n - n1; + self.emit_values(n1)?; + self.emit_values(n2) + } + _ => { + panic!("self.values shouldn't have more than 2 * max_entries_per_node entries"); + } } + } + + /// Pops the last node, and prepends it's values to 'self.values'. Used + /// to rebalance when we have insufficient values for a final node. The + /// node is decremented in the space map. + fn unshift_node(&mut self) -> Result<()> { + let ls = self.nodes.pop().unwrap(); + let (keys, values) = self.read_node(ls.block)?; + self.batcher.sm.lock().unwrap().dec(ls.block)?; + + let mut vals = VecDeque::new(); + + for i in 0..keys.len() { + // We only need to inc the values if the node was pre built. + if ls.shared { + self.value_rc.inc(&values[i])?; + } + vals.push_back((keys[i], values[i].clone())); + } + + vals.append(&mut self.values); + std::mem::swap(&mut self.values, &mut vals); Ok(()) } } //------------------------------------------ + +pub struct Builder { + engine: Arc, + sm: Arc>, + leaf_builder: NodeBuilder, +} + +const BATCH_SIZE: usize = 128; + +impl Builder { + pub fn new( + engine: Arc, + sm: Arc>, + value_rc: Box>, + ) -> Builder { + Builder { + engine: engine.clone(), + sm: sm.clone(), + leaf_builder: NodeBuilder::new( + WriteBatcher::new(engine.clone(), sm.clone(), BATCH_SIZE), + Box::new(LeafIO {}), + value_rc, + ), + } + } + + pub fn push_value(&mut self, k: u64, v: V) -> Result<()> { + self.leaf_builder.push_value(k, v) + } + + pub fn push_leaves(&mut self, leaves: &Vec) -> Result<()> { + self.leaf_builder.push_nodes(leaves) + } + + pub fn complete(self) -> Result { + let mut nodes = self.leaf_builder.complete()?; + + // Now we iterate, adding layers of internal nodes until we end + // up with a single root. + while nodes.len() > 1 { + let mut builder = NodeBuilder::new( + WriteBatcher::new(self.engine.clone(), self.sm.clone(), BATCH_SIZE), + Box::new(InternalIO {}), + Box::new(SMRefCounter { + sm: self.sm.clone(), + }), + ); + + for n in nodes { + builder.push_value(n.key, n.block)?; + } + + nodes = builder.complete()?; + } + + assert!(nodes.len() == 1); + Ok(nodes[0].block) + } +} + +//------------------------------------------ diff --git a/src/pdata/space_map.rs b/src/pdata/space_map.rs index d060c7b..deba551 100644 --- a/src/pdata/space_map.rs +++ b/src/pdata/space_map.rs @@ -2,8 +2,8 @@ use anyhow::{anyhow, Result}; use byteorder::{LittleEndian, WriteBytesExt}; use fixedbitset::FixedBitSet; use nom::{multi::count, number::complete::*, IResult}; -use std::sync::{Arc, Mutex}; use std::boxed::Box; +use std::sync::{Arc, Mutex}; use crate::io_engine::*; use crate::pdata::unpack::{Pack, Unpack}; @@ -226,13 +226,22 @@ pub trait SpaceMap { fn get_nr_allocated(&self) -> Result; fn get(&self, b: u64) -> Result; - // Returns the old ref count + /// Returns the old ref count fn set(&mut self, b: u64, v: u32) -> Result; fn inc(&mut self, begin: u64, len: u64) -> Result<()>; - // Finds a block with a zero reference count. Increments the - // count. + /// Returns true if the block is now free + fn dec(&mut self, b: u64) -> Result { + let old = self.get(b)?; + assert!(old > 0); + self.set(b, old - 1)?; + + Ok(old == 1) + } + + /// Finds a block with a zero reference count. Increments the + /// count. fn alloc(&mut self) -> Result>; } diff --git a/src/thin/restore.rs b/src/thin/restore.rs index 905393f..5135fab 100644 --- a/src/thin/restore.rs +++ b/src/thin/restore.rs @@ -1,9 +1,62 @@ use anyhow::Result; + +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::OpenOptions; use std::path::Path; use std::sync::Arc; use crate::report::*; +use crate::thin::block_time::*; +use crate::thin::device_detail::*; +use crate::thin::superblock::*; +use crate::thin::xml::{self, *}; + +//------------------------------------------ + +#[derive(Default)] +struct Pass1 { + // +} + +impl MetadataVisitor for Pass1 { + fn superblock_b(&mut self, sb: &xml::Superblock) -> Result { + todo!(); + } + + fn superblock_e(&mut self) -> Result { + todo!(); + } + + fn def_shared_b(&mut self, name: &str) -> Result { + todo!(); + } + + fn def_shared_e(&mut self) -> Result { + todo!(); + } + + fn device_b(&mut self, d: &Device) -> Result { + todo!(); + } + + fn device_e(&mut self) -> Result { + todo!(); + } + + fn map(&mut self, m: &Map) -> Result { + todo!(); + } + + fn ref_shared(&mut self, name: &str) -> Result { + todo!(); + } + + fn eof(&mut self) -> Result { + todo!(); + } +} + //------------------------------------------ pub struct ThinRestoreOptions<'a> { @@ -15,8 +68,16 @@ pub struct ThinRestoreOptions<'a> { //------------------------------------------ -pub fn restore(_opts: ThinRestoreOptions) -> Result<()> { - todo!(); +pub fn restore(opts: ThinRestoreOptions) -> Result<()> { + let input = OpenOptions::new() + .read(true) + .write(false) + .open(opts.input)?; + + let mut pass = Pass1::default(); + xml::read(input, &mut pass)?; + + Ok(()) } //------------------------------------------ diff --git a/src/write_batcher.rs b/src/write_batcher.rs index 029a721..f6ef877 100644 --- a/src/write_batcher.rs +++ b/src/write_batcher.rs @@ -7,9 +7,10 @@ use crate::pdata::space_map::*; //------------------------------------------ +#[derive(Clone)] pub struct WriteBatcher { - engine: Arc, - sm: Arc>, + pub engine: Arc, + pub sm: Arc>, batch_size: usize, queue: Vec, From 7d983e315553bffebf16f0e7f32bec47f2b2eb86 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Thu, 11 Mar 2021 18:24:10 +0800 Subject: [PATCH 02/11] [btree_builder (rust)] Fix the max_entries --- src/pdata/btree_builder.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pdata/btree_builder.rs b/src/pdata/btree_builder.rs index 636762b..180ac51 100644 --- a/src/pdata/btree_builder.rs +++ b/src/pdata/btree_builder.rs @@ -62,7 +62,8 @@ pub fn pack_node(node: &Node, w: &mut W) pub fn calc_max_entries() -> usize { let elt_size = 8 + V::disk_size() as usize; - ((BLOCK_SIZE - NodeHeader::disk_size() as usize) / elt_size) as usize + let total = ((BLOCK_SIZE - NodeHeader::disk_size() as usize) / elt_size) as usize + total / 3 * 3 } //------------------------------------------ From 0ff72374f8594bc3064fa388ee7861c06ee33724 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Fri, 12 Mar 2021 00:42:54 +0800 Subject: [PATCH 03/11] [btree_builder (rust)] Fix the max_entries --- src/pdata/btree_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pdata/btree_builder.rs b/src/pdata/btree_builder.rs index 180ac51..6ae1379 100644 --- a/src/pdata/btree_builder.rs +++ b/src/pdata/btree_builder.rs @@ -62,7 +62,7 @@ pub fn pack_node(node: &Node, w: &mut W) pub fn calc_max_entries() -> usize { let elt_size = 8 + V::disk_size() as usize; - let total = ((BLOCK_SIZE - NodeHeader::disk_size() as usize) / elt_size) as usize + let total = ((BLOCK_SIZE - NodeHeader::disk_size() as usize) / elt_size) as usize; total / 3 * 3 } From e0eb8fea876d6d49c9ac5d8abe7b29b5c66fc76b Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Fri, 12 Mar 2021 12:37:07 +0800 Subject: [PATCH 04/11] [btree (rust)] Show out-of-order keys --- src/pdata/btree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pdata/btree.rs b/src/pdata/btree.rs index 4351456..b7494f4 100644 --- a/src/pdata/btree.rs +++ b/src/pdata/btree.rs @@ -558,7 +558,7 @@ pub fn unpack_node( for k in &keys { if let Some(l) = last { if k <= l { - return Err(node_err(&path, "keys out of order")); + return Err(node_err(&path, &format!("keys out of order: {} <= {}", k, l))); } } From 040e3bfc2d750790e5f583f36953e6317fa9b5d1 Mon Sep 17 00:00:00 2001 From: Joe Thornber Date: Wed, 24 Mar 2021 14:20:20 +0000 Subject: [PATCH 05/11] Lot's of work on thin_restore --- src/bin/thin_restore.rs | 2 + src/checksum.rs | 1 - src/io_engine.rs | 8 + src/lib.rs | 1 + src/pdata/btree.rs | 2 + src/pdata/btree_builder.rs | 121 +++++------ src/pdata/mod.rs | 1 + src/pdata/space_map.rs | 218 +------------------- src/pdata/space_map_disk.rs | 399 ++++++++++++++++++++++++++++++++++++ src/thin/block_time.rs | 9 + src/thin/check.rs | 1 + src/thin/device_detail.rs | 24 ++- src/thin/dump.rs | 8 +- src/thin/restore.rs | 258 +++++++++++++++++++++-- src/thin/superblock.rs | 94 ++++++--- src/thin/xml.rs | 30 ++- src/write_batcher.rs | 29 ++- 17 files changed, 858 insertions(+), 348 deletions(-) create mode 100644 src/pdata/space_map_disk.rs diff --git a/src/bin/thin_restore.rs b/src/bin/thin_restore.rs index b774480..93f62e9 100644 --- a/src/bin/thin_restore.rs +++ b/src/bin/thin_restore.rs @@ -27,6 +27,7 @@ fn main() { .help("Specify the input xml") .short("i") .long("input") + .value_name("INPUT") .required(true), ) .arg( @@ -34,6 +35,7 @@ fn main() { .help("Specify the output device to check") .short("o") .long("output") + .value_name("OUTPUT") .required(true), ) .arg( diff --git a/src/checksum.rs b/src/checksum.rs index 0ffbd47..edb5e0a 100644 --- a/src/checksum.rs +++ b/src/checksum.rs @@ -6,7 +6,6 @@ use std::io::Cursor; const BLOCK_SIZE: u64 = 4096; #[allow(dead_code)] -const MAGIC: u64 = 0xa537a0aa6309ef77; const SUPERBLOCK_CSUM_XOR: u32 = 160774; const BITMAP_CSUM_XOR: u32 = 240779; const INDEX_CSUM_XOR: u32 = 160478; diff --git a/src/io_engine.rs b/src/io_engine.rs index 879c294..89b38d6 100644 --- a/src/io_engine.rs +++ b/src/io_engine.rs @@ -26,6 +26,8 @@ pub struct Block { } impl Block { + // Creates a new block that corresponds to the given location. The + // memory is not initialised. pub fn new(loc: u64) -> Block { let layout = Layout::from_size_align(BLOCK_SIZE, ALIGN).unwrap(); let ptr = unsafe { alloc(layout) }; @@ -42,6 +44,12 @@ impl Block { pub fn get_data<'a>(&self) -> &'a mut [u8] { unsafe { std::slice::from_raw_parts_mut::<'a>(self.data, BLOCK_SIZE) } } + + pub fn zero(&mut self) { + unsafe { + std::ptr::write_bytes(self.data, 0, BLOCK_SIZE); + } + } } impl Drop for Block { diff --git a/src/lib.rs b/src/lib.rs index 451257f..0ff3841 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ pub mod cache; pub mod checksum; pub mod file_utils; pub mod io_engine; +pub mod math; pub mod pack; pub mod pdata; pub mod report; diff --git a/src/pdata/btree.rs b/src/pdata/btree.rs index 4351456..833ff50 100644 --- a/src/pdata/btree.rs +++ b/src/pdata/btree.rs @@ -539,6 +539,7 @@ pub fn unpack_node( } if !is_root { + /* let min = header.max_entries / 3; if header.nr_entries < min { return Err(node_err_s( @@ -549,6 +550,7 @@ pub fn unpack_node( ), )); } + */ } } diff --git a/src/pdata/btree_builder.rs b/src/pdata/btree_builder.rs index 2daf04f..cbae889 100644 --- a/src/pdata/btree_builder.rs +++ b/src/pdata/btree_builder.rs @@ -21,6 +21,14 @@ pub trait RefCounter { fn dec(&mut self, v: &Value) -> Result<()>; } +pub struct NoopRC {} + +impl RefCounter for NoopRC { + fn get(&self, _v: &Value) -> Result {Ok(0)} + fn inc(&mut self, _v: &Value) -> Result<()> {Ok(())} + fn dec(&mut self, _v: &Value) -> Result<()> {Ok(())} +} + /// Wraps a space map up to become a RefCounter. struct SMRefCounter { sm: Arc>, @@ -126,12 +134,12 @@ fn write_node_(w: &mut WriteBatcher, mut node: Node) -> Res let keys = node.get_keys(); let first_key = keys.first().unwrap_or(&0u64).clone(); - let loc = w.alloc()?; - node.set_block(loc); + let b = w.alloc()?; + node.set_block(b.loc); - let b = Block::new(loc); let mut cursor = Cursor::new(b.get_data()); pack_node(&node, &mut cursor)?; + let loc = b.loc; w.write(b, checksum::BT::NODE)?; Ok(WriteResult { first_key, loc }) @@ -149,7 +157,7 @@ pub trait NodeIO { ) -> Result<(Vec, Vec)>; } -struct LeafIO {} +pub struct LeafIO {} impl NodeIO for LeafIO { fn write(&self, w: &mut WriteBatcher, keys: Vec, values: Vec) -> Result { @@ -229,7 +237,6 @@ impl NodeIO for InternalIO { /// Care is taken to make sure that all nodes are at least half full unless there's /// only a single node. pub struct NodeBuilder { - batcher: WriteBatcher, nio: Box>, value_rc: Box>, max_entries_per_node: usize, @@ -252,15 +259,13 @@ pub struct NodeSummary { shared: bool, } -impl NodeBuilder { +impl<'a, V: Pack + Unpack + Clone> NodeBuilder { /// Create a new NodeBuilder pub fn new( - batcher: WriteBatcher, nio: Box>, value_rc: Box>, ) -> Self { NodeBuilder { - batcher, nio, value_rc, max_entries_per_node: calc_max_entries::(), @@ -270,12 +275,12 @@ impl NodeBuilder { } /// Push a single value. This may emit a new node, hence the Result /// return type. The value's ref count will be incremented. - pub fn push_value(&mut self, key: u64, val: V) -> Result<()> { + pub fn push_value(&mut self, w: &mut WriteBatcher, key: u64, val: V) -> Result<()> { // Have we got enough values to emit a node? We try and keep // at least max_entries_per_node entries unflushed so we // can ensure the final node is balanced properly. if self.values.len() == self.max_entries_per_node * 2 { - self.emit_node()?; + self.emit_node(w)?; } self.value_rc.inc(&val)?; @@ -289,7 +294,7 @@ impl NodeBuilder { /// 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, nodes: &Vec) -> Result<()> { + pub fn push_nodes(&mut self, w: &mut WriteBatcher, nodes: &Vec) -> Result<()> { assert!(nodes.len() > 0); // As a sanity check we make sure that all the shared nodes contain the @@ -305,7 +310,7 @@ impl NodeBuilder { if 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(nodes.get(0).unwrap().block)?; + let (keys, values) = self.read_node(w, nodes.get(0).unwrap().block)?; for i in 0..keys.len() { self.value_rc.inc(&values[i])?; @@ -313,21 +318,21 @@ impl NodeBuilder { } // Flush all the values. - self.emit_all()?; + self.emit_all(w)?; // Add the remaining nodes. for i in 1..nodes.len() { let n = nodes.get(i).unwrap(); - self.batcher.sm.lock().unwrap().inc(n.block, 1)?; + w.sm.lock().unwrap().inc(n.block, 1)?; self.nodes.push(n.clone()); } } else { // Flush all the values. - self.emit_all()?; + self.emit_all(w)?; // add the nodes for n in nodes { - self.batcher.sm.lock().unwrap().inc(n.block, 1)?; + w.sm.lock().unwrap().inc(n.block, 1)?; self.nodes.push(n.clone()); } } @@ -337,16 +342,21 @@ impl NodeBuilder { /// Signal that no more values or nodes will be pushed. Returns a /// vector of the built nodes. Consumes the builder. - pub fn complete(mut self) -> Result> { + pub fn complete(mut self, w: &mut WriteBatcher) -> Result> { let half_full = self.max_entries_per_node / 2; if (self.nodes.len() > 0) && (self.values.len() < half_full) { // 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()?; + self.unshift_node(w)?; } - self.emit_all()?; + self.emit_all(w)?; + + if self.nodes.len() == 0 { + self.emit_empty_leaf(w)? + } + Ok(self.nodes) } @@ -354,13 +364,13 @@ impl NodeBuilder { // We're only interested in the keys and values from the node, and // not whether it's a leaf or internal node. - fn read_node(&self, block: u64) -> Result<(Vec, Vec)> { - self.nio.read(&self.batcher.engine, block) + fn read_node(&self, w: &WriteBatcher, block: u64) -> Result<(Vec, Vec)> { + self.nio.read(&w.engine, block) } /// Writes a node with the first 'nr_entries' values. - fn emit_values(&mut self, nr_entries: usize) -> Result<()> { - assert!(self.values.len() <= nr_entries); + fn emit_values(&mut self, w: &mut WriteBatcher, nr_entries: usize) -> Result<()> { + assert!(nr_entries <= self.values.len()); // Write the node let mut keys = Vec::new(); @@ -372,7 +382,7 @@ impl NodeBuilder { values.push(v); } - let wresult = self.nio.write(&mut self.batcher, keys, values)?; + let wresult = self.nio.write(w, keys, values)?; // Push a summary to the 'nodes' vector. self.nodes.push(NodeSummary { @@ -385,13 +395,13 @@ impl NodeBuilder { } /// Writes a full node. - fn emit_node(&mut self) -> Result<()> { - self.emit_values(self.max_entries_per_node) + fn emit_node(&mut self, w: &mut WriteBatcher) -> Result<()> { + self.emit_values(w, self.max_entries_per_node) } /// Emits all remaining values. Panics if there are more than 2 * /// max_entries_per_node values. - fn emit_all(&mut self) -> Result<()> { + fn emit_all(&mut self, w: &mut WriteBatcher) -> Result<()> { match self.values.len() { 0 => { // There's nothing to emit @@ -399,14 +409,14 @@ impl NodeBuilder { } n if n <= self.max_entries_per_node => { // Emit a single node. - self.emit_values(n) + self.emit_values(w, n) } n if n <= self.max_entries_per_node * 2 => { // Emit two nodes. let n1 = n / 2; let n2 = n - n1; - self.emit_values(n1)?; - self.emit_values(n2) + self.emit_values(w, n1)?; + self.emit_values(w, n2) } _ => { panic!("self.values shouldn't have more than 2 * max_entries_per_node entries"); @@ -414,13 +424,17 @@ impl NodeBuilder { } } + fn emit_empty_leaf(&mut self, w: &mut WriteBatcher) -> Result<()> { + self.emit_values(w, 0) + } + /// Pops the last node, and prepends it's values to 'self.values'. Used /// to rebalance when we have insufficient values for a final node. The /// node is decremented in the space map. - fn unshift_node(&mut self) -> Result<()> { + fn unshift_node(&mut self, w: &mut WriteBatcher) -> Result<()> { let ls = self.nodes.pop().unwrap(); - let (keys, values) = self.read_node(ls.block)?; - self.batcher.sm.lock().unwrap().dec(ls.block)?; + let (keys, values) = self.read_node(w, ls.block)?; + w.sm.lock().unwrap().dec(ls.block)?; let mut vals = VecDeque::new(); @@ -442,57 +456,47 @@ impl NodeBuilder { //------------------------------------------ pub struct Builder { - engine: Arc, - sm: Arc>, leaf_builder: NodeBuilder, } -const BATCH_SIZE: usize = 128; - impl Builder { pub fn new( - engine: Arc, - sm: Arc>, value_rc: Box>, ) -> Builder { Builder { - engine: engine.clone(), - sm: sm.clone(), leaf_builder: NodeBuilder::new( - WriteBatcher::new(engine.clone(), sm.clone(), BATCH_SIZE), Box::new(LeafIO {}), value_rc, ), } } - pub fn push_value(&mut self, k: u64, v: V) -> Result<()> { - self.leaf_builder.push_value(k, v) + pub fn push_value(&mut self, w: &mut WriteBatcher, k: u64, v: V) -> Result<()> { + self.leaf_builder.push_value(w, k, v) } - pub fn push_leaves(&mut self, leaves: &Vec) -> Result<()> { - self.leaf_builder.push_nodes(leaves) + pub fn push_leaves(&mut self, w: &mut WriteBatcher, leaves: &Vec) -> Result<()> { + self.leaf_builder.push_nodes(w, leaves) } - pub fn complete(self) -> Result { - let mut nodes = self.leaf_builder.complete()?; + pub fn complete(self, w: &mut WriteBatcher) -> Result { + let mut nodes = self.leaf_builder.complete(w)?; // Now we iterate, adding layers of internal nodes until we end // up with a single root. while nodes.len() > 1 { let mut builder = NodeBuilder::new( - WriteBatcher::new(self.engine.clone(), self.sm.clone(), BATCH_SIZE), Box::new(InternalIO {}), Box::new(SMRefCounter { - sm: self.sm.clone(), + sm: w.sm.clone(), }), ); for n in nodes { - builder.push_value(n.key, n.block)?; + builder.push_value(w, n.key, n.block)?; } - nodes = builder.complete()?; + nodes = builder.complete(w)?; } assert!(nodes.len() == 1); @@ -502,16 +506,3 @@ impl Builder { //------------------------------------------ -/* -======= -fn write_node_(w: &mut WriteBatcher, mut node: Node) -> Result<(u64, u64)> { - let keys = node.get_keys(); - let first_key = *keys.first().unwrap_or(&0u64); ->>>>>>> main - -======= -fn write_node_(w: &mut WriteBatcher, mut node: Node) -> Result<(u64, u64)> { - let keys = node.get_keys(); - let first_key = *keys.first().unwrap_or(&0u64); ->>>>>>> main -*/ diff --git a/src/pdata/mod.rs b/src/pdata/mod.rs index 370d244..70d5d39 100644 --- a/src/pdata/mod.rs +++ b/src/pdata/mod.rs @@ -7,5 +7,6 @@ pub mod btree_merge; pub mod btree_leaf_walker; pub mod btree_walker; pub mod space_map; +pub mod space_map_disk; pub mod unpack; diff --git a/src/pdata/space_map.rs b/src/pdata/space_map.rs index deba551..5f1f2f0 100644 --- a/src/pdata/space_map.rs +++ b/src/pdata/space_map.rs @@ -1,224 +1,8 @@ -use anyhow::{anyhow, Result}; -use byteorder::{LittleEndian, WriteBytesExt}; +use anyhow::Result; use fixedbitset::FixedBitSet; -use nom::{multi::count, number::complete::*, IResult}; use std::boxed::Box; use std::sync::{Arc, Mutex}; -use crate::io_engine::*; -use crate::pdata::unpack::{Pack, Unpack}; - -//------------------------------------------ - -#[derive(Debug)] -pub struct SMRoot { - pub nr_blocks: u64, - pub nr_allocated: u64, - pub bitmap_root: u64, - pub ref_count_root: u64, -} - -pub fn unpack_root(data: &[u8]) -> Result { - match SMRoot::unpack(data) { - Err(_e) => Err(anyhow!("couldn't parse SMRoot")), - Ok((_i, v)) => Ok(v), - } -} - -impl Unpack for SMRoot { - fn disk_size() -> u32 { - 32 - } - - fn unpack(data: &[u8]) -> IResult<&[u8], SMRoot> { - let (i, nr_blocks) = le_u64(data)?; - let (i, nr_allocated) = le_u64(i)?; - let (i, bitmap_root) = le_u64(i)?; - let (i, ref_count_root) = le_u64(i)?; - - Ok(( - i, - SMRoot { - nr_blocks, - nr_allocated, - bitmap_root, - ref_count_root, - }, - )) - } -} - -//------------------------------------------ - -#[derive(Clone, Copy, Debug)] -pub struct IndexEntry { - pub blocknr: u64, - pub nr_free: u32, - pub none_free_before: u32, -} - -impl Unpack for IndexEntry { - fn disk_size() -> u32 { - 16 - } - - fn unpack(data: &[u8]) -> IResult<&[u8], Self> { - let (i, blocknr) = le_u64(data)?; - let (i, nr_free) = le_u32(i)?; - let (i, none_free_before) = le_u32(i)?; - - Ok(( - i, - IndexEntry { - blocknr, - nr_free, - none_free_before, - }, - )) - } -} - -//------------------------------------------ - -pub const MAX_METADATA_BITMAPS: usize = 255; - -pub struct MetadataIndex { - pub indexes: Vec, -} - -impl Unpack for MetadataIndex { - fn disk_size() -> u32 { - BLOCK_SIZE as u32 - } - - fn unpack(data: &[u8]) -> IResult<&[u8], Self> { - let (i, _csum) = le_u32(data)?; - let (i, _padding) = le_u32(i)?; - let (i, _blocknr) = le_u64(i)?; - let (i, indexes) = count(IndexEntry::unpack, MAX_METADATA_BITMAPS)(i)?; - - Ok((i, MetadataIndex { indexes })) - } -} - -//------------------------------------------ - -#[derive(Debug)] -pub struct BitmapHeader { - pub csum: u32, - pub not_used: u32, - pub blocknr: u64, -} - -impl Unpack for BitmapHeader { - fn disk_size() -> u32 { - 16 - } - - fn unpack(data: &[u8]) -> IResult<&[u8], Self> { - let (i, csum) = le_u32(data)?; - let (i, not_used) = le_u32(i)?; - let (i, blocknr) = le_u64(i)?; - - Ok(( - i, - BitmapHeader { - csum, - not_used, - blocknr, - }, - )) - } -} - -impl Pack for BitmapHeader { - fn pack(&self, out: &mut W) -> Result<()> { - out.write_u32::(self.csum)?; - out.write_u32::(self.not_used)?; - out.write_u64::(self.blocknr)?; - Ok(()) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum BitmapEntry { - Small(u8), - Overflow, -} - -#[derive(Debug)] -pub struct Bitmap { - pub header: BitmapHeader, - pub entries: Vec, -} - -impl Unpack for Bitmap { - fn disk_size() -> u32 { - BLOCK_SIZE as u32 - } - - fn unpack(data: &[u8]) -> IResult<&[u8], Self> { - let (mut i, header) = BitmapHeader::unpack(data)?; - - let nr_words = (BLOCK_SIZE - BitmapHeader::disk_size() as usize) / 8; - let mut entries = Vec::with_capacity(nr_words * 32); - for _w in 0..nr_words { - let (tmp, mut word) = le_u64(i)?; - - for _b in 0..32 { - let val = word & 0x3; - word >>= 2; - - // The bits are stored with the high bit at b * 2 + 1, - // and low at b *2. So we have to interpret this val. - entries.push(match val { - 0 => BitmapEntry::Small(0), - 1 => BitmapEntry::Small(2), - 2 => BitmapEntry::Small(1), - _ => BitmapEntry::Overflow, - }); - } - - i = tmp; - } - - Ok((i, Bitmap { header, entries })) - } -} - -impl Pack for Bitmap { - fn pack(&self, out: &mut W) -> Result<()> { - use BitmapEntry::*; - BitmapHeader::pack(&self.header, out)?; - - for chunk in self.entries.chunks(32) { - let mut w = 0u64; - for e in chunk { - w >>= 2; - match e { - Small(0) => {} - Small(1) => { - w |= 0x2 << 62; - } - Small(2) => { - w |= 0x1 << 62; - } - Small(_) => { - return Err(anyhow!("Bad small value in bitmap entry")); - } - Overflow => { - w |= 0x3 << 62; - } - } - } - - u64::pack(&w, out)?; - } - - Ok(()) - } -} - //------------------------------------------ pub trait SpaceMap { diff --git a/src/pdata/space_map_disk.rs b/src/pdata/space_map_disk.rs new file mode 100644 index 0000000..b89de29 --- /dev/null +++ b/src/pdata/space_map_disk.rs @@ -0,0 +1,399 @@ +use anyhow::{anyhow, Result}; +use byteorder::{LittleEndian, WriteBytesExt}; +use nom::{number::complete::*, IResult}; +use std::io::Cursor; +use std::collections::BTreeMap; + +use crate::checksum; +use crate::io_engine::*; +use crate::math::*; +use crate::pdata::btree_builder::*; +use crate::pdata::space_map::*; +use crate::pdata::unpack::*; +use crate::write_batcher::*; + +//-------------------------------- + +const MAX_METADATA_BITMAPS: usize = 255; +// const MAX_METADATA_BLOCKS: u64 = 255 * ((1 << 14) - 64); +const ENTRIES_PER_BYTE: usize = 4; +const ENTRIES_PER_BITMAP: usize = WORDS_PER_BITMAP * 8 * ENTRIES_PER_BYTE; + +//-------------------------------- + +#[derive(Clone, Copy, Debug)] +pub struct IndexEntry { + pub blocknr: u64, + pub nr_free: u32, + pub none_free_before: u32, +} + +impl Unpack for IndexEntry { + fn disk_size() -> u32 { + 16 + } + + fn unpack(i: &[u8]) -> IResult<&[u8], IndexEntry> { + let (i, blocknr) = le_u64(i)?; + let (i, nr_free) = le_u32(i)?; + let (i, none_free_before) = le_u32(i)?; + + Ok(( + i, + IndexEntry { + blocknr, + nr_free, + none_free_before, + }, + )) + } +} + +impl Pack for IndexEntry { + fn pack(&self, w: &mut W) -> Result<()> { + w.write_u64::(self.blocknr)?; + w.write_u32::(self.nr_free)?; + w.write_u32::(self.none_free_before)?; + Ok(()) + } +} + +//-------------------------------- + +pub struct MetadataIndex { + pub blocknr: u64, + pub indexes: Vec, +} + +impl Unpack for MetadataIndex { + fn disk_size() -> u32 { + BLOCK_SIZE as u32 + } + + fn unpack(i: &[u8]) -> IResult<&[u8], MetadataIndex> { + // FIXME: check the checksum + let (i, _csum) = le_u32(i)?; + let (i, _padding) = le_u32(i)?; + let (i, blocknr) = le_u64(i)?; + let (i, indexes) = nom::multi::count(IndexEntry::unpack, MAX_METADATA_BITMAPS)(i)?; + + Ok((i, MetadataIndex { blocknr, indexes })) + } +} + +impl Pack for MetadataIndex { + fn pack(&self, w: &mut W) -> Result<()> { + w.write_u32::(0)?; // csum + w.write_u32::(0)?; // padding + w.write_u64::(self.blocknr)?; + + assert!(self.indexes.len() <= MAX_METADATA_BITMAPS); + + for ie in &self.indexes { + ie.pack(w)?; + } + + Ok(()) + } +} + +//-------------------------------- + +const WORDS_PER_BITMAP: usize = (BLOCK_SIZE - 16) / 8; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BitmapEntry { + Small(u8), + Overflow, +} + +#[derive(Debug)] +pub struct Bitmap { + pub blocknr: u64, + pub entries: Vec, +} + +impl Unpack for Bitmap { + fn disk_size() -> u32 { + BLOCK_SIZE as u32 + } + + fn unpack(data: &[u8]) -> IResult<&[u8], Self> { + let (i, _csum) = le_u32(data)?; + let (i, _not_used) = le_u32(i)?; + let (mut i, blocknr) = le_u64(i)?; + + let header_size = 16; + let nr_words = (BLOCK_SIZE - header_size) / 8; + let mut entries = Vec::with_capacity(nr_words * 32); + for _w in 0..nr_words { + let (tmp, mut word) = le_u64(i)?; + + for _b in 0..32 { + let val = word & 0x3; + word >>= 2; + + // The bits are stored with the high bit at b * 2 + 1, + // and low at b *2. So we have to interpret this val. + entries.push(match val { + 0 => BitmapEntry::Small(0), + 1 => BitmapEntry::Small(2), + 2 => BitmapEntry::Small(1), + _ => BitmapEntry::Overflow, + }); + } + + i = tmp; + } + + Ok((i, Bitmap { blocknr, entries })) + } +} + +impl Pack for Bitmap { + fn pack(&self, out: &mut W) -> Result<()> { + use BitmapEntry::*; + + out.write_u32::(0)?; + out.write_u32::(0)?; + out.write_u64::(self.blocknr)?; + + for chunk in self.entries.chunks(32) { + let mut w = 0u64; + for e in chunk { + w >>= 2; + match e { + Small(0) => {} + Small(1) => { + w |= 0x2 << 62; + } + Small(2) => { + w |= 0x1 << 62; + } + Small(_) => { + return Err(anyhow!("Bad small value in bitmap entry")); + } + Overflow => { + w |= 0x3 << 62; + } + } + } + + u64::pack(&w, out)?; + } + + Ok(()) + } +} + +//-------------------------------- + +#[derive(Debug)] +pub struct SMRoot { + pub nr_blocks: u64, + pub nr_allocated: u64, + pub bitmap_root: u64, + pub ref_count_root: u64, +} + +impl Unpack for SMRoot { + fn disk_size() -> u32 { + 32 + } + + fn unpack(i: &[u8]) -> IResult<&[u8], Self> { + let (i, nr_blocks) = le_u64(i)?; + let (i, nr_allocated) = le_u64(i)?; + let (i, bitmap_root) = le_u64(i)?; + let (i, ref_count_root) = le_u64(i)?; + + Ok(( + i, + SMRoot { + nr_blocks, + nr_allocated, + bitmap_root, + ref_count_root, + }, + )) + } +} + +pub fn unpack_root(data: &[u8]) -> Result { + match SMRoot::unpack(data) { + Err(_e) => Err(anyhow!("couldn't parse SMRoot")), + Ok((_i, v)) => Ok(v), + } +} + +impl Pack for SMRoot { + fn pack(&self, w: &mut W) -> Result<()> { + w.write_u64::(self.nr_blocks)?; + w.write_u64::(self.nr_allocated)?; + w.write_u64::(self.bitmap_root)?; + w.write_u64::(self.ref_count_root)?; + + Ok(()) + } +} + +//-------------------------------- + +pub fn write_common(w: &mut WriteBatcher, sm: &dyn SpaceMap) -> Result<(Vec, u64)> { + use BitmapEntry::*; + + let mut index_entries = Vec::new(); + let mut overflow_builder: Builder = Builder::new(Box::new(NoopRC {})); + + // how many bitmaps do we need? + for bm in 0..div_up(sm.get_nr_blocks()? as usize, ENTRIES_PER_BITMAP) { + let mut entries = Vec::with_capacity(ENTRIES_PER_BITMAP); + let mut first_free: Option = None; + let mut nr_free: u32 = 0; + for i in 0..ENTRIES_PER_BITMAP { + let b: u64 = ((bm * ENTRIES_PER_BITMAP) as u64) + i as u64; + if b > sm.get_nr_blocks()? { + break; + } + let rc = sm.get(b)?; + let e = match rc { + 0 => { + nr_free += 1; + if first_free.is_none() { + first_free = Some(i as u32); + } + Small(0) + } + 1 => Small(1), + 2 => Small(2), + _ => { + overflow_builder.push_value(w, b as u64, rc)?; + Overflow + } + }; + entries.push(e); + } + + // allocate a new block + let b = w.alloc()?; + let mut cursor = Cursor::new(b.get_data()); + + // write the bitmap to it + let blocknr = b.loc; + let bitmap = Bitmap { blocknr, entries }; + bitmap.pack(&mut cursor)?; + w.write(b, checksum::BT::BITMAP)?; + + // Insert into the index tree + let ie = IndexEntry { + blocknr, + nr_free, + none_free_before: first_free.unwrap_or(ENTRIES_PER_BITMAP as u32), + }; + index_entries.push(ie); + } + + let ref_count_root = overflow_builder.complete(w)?; + Ok((index_entries, ref_count_root)) +} + +pub fn write_disk_sm(w: &mut WriteBatcher, sm: &dyn SpaceMap) -> Result { + let (index_entries, ref_count_root) = write_common(w, sm)?; + + let mut index_builder: Builder = Builder::new(Box::new(NoopRC {})); + for (i, ie) in index_entries.iter().enumerate() { + index_builder.push_value(w, i as u64, *ie)?; + } + + let bitmap_root = index_builder.complete(w)?; + + Ok(SMRoot { + nr_blocks: sm.get_nr_blocks()?, + nr_allocated: sm.get_nr_allocated()?, + bitmap_root, + ref_count_root, + }) +} + +//---------------------------- + +fn block_to_bitmap(b: u64) -> usize { + (b / ENTRIES_PER_BITMAP as u64) as usize +} + +fn adjust_counts(w: &mut WriteBatcher, ie: &IndexEntry, allocs: &[u64]) -> Result { + use BitmapEntry::*; + + let mut first_free = ie.none_free_before; + let mut nr_free = ie.nr_free - allocs.len() as u32; + + // Read the bitmap + let bitmap_block = w.engine.read(ie.blocknr)?; + let (_, mut bitmap) = Bitmap::unpack(bitmap_block.get_data())?; + + // Update all the entries + for a in allocs { + if first_free == *a as u32 { + first_free = *a as u32 + 1; + } + + if bitmap.entries[*a as usize] == Small(0) { + nr_free -= 1; + } + + bitmap.entries[*a as usize] = Small(1); + } + + // Write the bitmap + let mut cur = Cursor::new(bitmap_block.get_data()); + bitmap.pack(&mut cur)?; + w.write(bitmap_block, checksum::BT::BITMAP)?; + + // Return the adjusted index entry + Ok (IndexEntry { + blocknr: ie.blocknr, + nr_free, + none_free_before: first_free, + }) +} + +pub fn write_metadata_sm(w: &mut WriteBatcher, sm: &dyn SpaceMap) -> Result { + w.clear_allocations(); + let (mut indexes, ref_count_root) = write_common(w, sm)?; + + let bitmap_root = w.alloc()?; + + // Now we need to patch up the counts for the metadata that was used for storing + // the space map itself. These ref counts all went from 0 to 1. + let allocations = w.clear_allocations(); + + // Sort the allocations by bitmap + 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); + } + + for (bitmap, allocs) in by_bitmap { + indexes[bitmap] = adjust_counts(w, &indexes[bitmap], &allocs)?; + } + + // Write out the metadata index + let metadata_index = MetadataIndex { + blocknr: bitmap_root.loc, + indexes + }; + let mut cur = Cursor::new(bitmap_root.get_data()); + metadata_index.pack(&mut cur)?; + let loc = bitmap_root.loc; + w.write(bitmap_root, checksum::BT::INDEX)?; + + Ok(SMRoot { + nr_blocks: sm.get_nr_blocks()?, + nr_allocated: sm.get_nr_allocated()?, + bitmap_root: loc, + ref_count_root, + }) +} + +//-------------------------------- diff --git a/src/thin/block_time.rs b/src/thin/block_time.rs index 3afe28c..0df343e 100644 --- a/src/thin/block_time.rs +++ b/src/thin/block_time.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use byteorder::WriteBytesExt; use nom::{number::complete::*, IResult}; use std::fmt; @@ -31,6 +33,13 @@ impl Unpack for BlockTime { } } +impl Pack for BlockTime { + fn pack(&self, data: &mut W) -> Result<()> { + let bt: u64 = (self.block << 24) | self.time as u64; + bt.pack(data) + } +} + impl fmt::Display for BlockTime { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} @ {}", self.block, self.time) diff --git a/src/thin/check.rs b/src/thin/check.rs index cb25257..69223c4 100644 --- a/src/thin/check.rs +++ b/src/thin/check.rs @@ -12,6 +12,7 @@ use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine}; 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::*; use crate::thin::block_time::*; diff --git a/src/thin/device_detail.rs b/src/thin/device_detail.rs index b375d88..9292c95 100644 --- a/src/thin/device_detail.rs +++ b/src/thin/device_detail.rs @@ -1,7 +1,9 @@ +use anyhow::Result; +use byteorder::{LittleEndian, WriteBytesExt}; +use nom::{number::complete::*, IResult}; use std::fmt; use crate::pdata::unpack::*; -use nom::{number::complete::*, IResult}; //------------------------------------------ @@ -15,11 +17,11 @@ pub struct DeviceDetail { impl fmt::Display for DeviceDetail { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "mapped = {}, trans = {}, create = {}, snap = {}", - self.mapped_blocks, - self.transaction_id, - self.creation_time, - self.snapshotted_time)?; + write!( + f, + "mapped = {}, trans = {}, create = {}, snap = {}", + self.mapped_blocks, self.transaction_id, self.creation_time, self.snapshotted_time + )?; Ok(()) } } @@ -47,4 +49,14 @@ impl Unpack for DeviceDetail { } } +impl Pack for DeviceDetail { + fn pack(&self, w: &mut W) -> Result<()> { + w.write_u64::(self.mapped_blocks)?; + w.write_u64::(self.transaction_id)?; + w.write_u32::(self.creation_time)?; + w.write_u32::(self.snapshotted_time)?; + Ok(()) + } +} + //------------------------------------------ diff --git a/src/thin/dump.rs b/src/thin/dump.rs index 1385902..f12a57a 100644 --- a/src/thin/dump.rs +++ b/src/thin/dump.rs @@ -11,6 +11,7 @@ use crate::pdata::btree::{self, *}; use crate::pdata::btree_leaf_walker::*; use crate::pdata::btree_walker::*; use crate::pdata::space_map::*; +use crate::pdata::space_map_disk::*; use crate::pdata::unpack::*; use crate::report::*; use crate::thin::block_time::*; @@ -287,7 +288,7 @@ fn find_shared_nodes( // We have to get the leaves so w is consumed and the &mut on sm // is dropped. - let leaves = w.get_leaves(); + let _leaves = w.get_leaves(); let mut shared = BTreeSet::new(); { for i in 0..sm.get_nr_blocks().unwrap() { @@ -297,6 +298,8 @@ fn find_shared_nodes( } } +/* + // FIXME: why?!! // we're not interested in leaves (roots will get re-added later). { for i in 0..leaves.len() { @@ -305,6 +308,7 @@ fn find_shared_nodes( } } } + */ Ok((shared, sm)) } @@ -616,9 +620,11 @@ pub fn dump(opts: ThinDumpOptions) -> Result<()> { let sb = read_superblock(ctx.engine.as_ref(), SUPERBLOCK_LOCATION)?; let md = build_metadata(&ctx, &sb)?; +/* ctx.report .set_title("Optimising metadata to improve leaf packing"); let md = optimise_metadata(md)?; + */ dump_metadata(&ctx, &sb, &md) } diff --git a/src/thin/restore.rs b/src/thin/restore.rs index 5135fab..9094b02 100644 --- a/src/thin/restore.rs +++ b/src/thin/restore.rs @@ -1,62 +1,203 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use std::fs::OpenOptions; use std::path::Path; use std::sync::Arc; +use crate::io_engine::*; +use crate::pdata::btree_builder::*; +use crate::pdata::space_map::*; use crate::report::*; - use crate::thin::block_time::*; use crate::thin::device_detail::*; -use crate::thin::superblock::*; +use crate::thin::superblock::{self, *}; use crate::thin::xml::{self, *}; +use crate::write_batcher::*; //------------------------------------------ -#[derive(Default)] -struct Pass1 { - // +enum MappedSection { + Def(String), + Dev(u32), } -impl MetadataVisitor for Pass1 { +impl std::fmt::Display for MappedSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MappedSection::Def(name) => write!(f, "Def {}", name), + MappedSection::Dev(thin_id) => write!(f, "Device {}", thin_id), + } + } +} + +struct Pass1Result { + sb: Option, + devices: BTreeMap)>, +} + +struct Pass1<'a> { + w: &'a mut WriteBatcher, + + current_dev: Option, + sub_trees: BTreeMap>, + + // The builder for the current shared sub tree or device + map: Option<(MappedSection, NodeBuilder)>, + + result: Pass1Result, +} + +impl<'a> Pass1<'a> { + fn new(w: &'a mut WriteBatcher) -> Self { + Pass1 { + w, + current_dev: None, + sub_trees: BTreeMap::new(), + map: None, + result: Pass1Result { + sb: None, + devices: BTreeMap::new(), + }, + } + } + + fn get_result(self) -> Pass1Result { + self.result + } + + fn begin_section(&mut self, section: MappedSection) -> Result { + if let Some((outer, _)) = self.map.as_ref() { + let msg = format!( + "Nested subtrees are not allowed '{}' within '{}'", + section, outer + ); + return Err(anyhow!(msg)); + } + + let value_rc = Box::new(NoopRC {}); + let leaf_builder = NodeBuilder::new(Box::new(LeafIO {}), value_rc); + + self.map = Some((section, leaf_builder)); + Ok(Visit::Continue) + } + + fn end_section(&mut self) -> Result<(MappedSection, Vec)> { + let mut current = None; + std::mem::swap(&mut self.map, &mut current); + + if let Some((name, nodes)) = current { + Ok((name, nodes.complete(self.w)?)) + } else { + let msg = format!("Unbalanced tag"); + Err(anyhow!(msg)) + } + } +} + +impl<'a> MetadataVisitor for Pass1<'a> { fn superblock_b(&mut self, sb: &xml::Superblock) -> Result { - todo!(); + self.result.sb = Some(sb.clone()); + Ok(Visit::Continue) } fn superblock_e(&mut self) -> Result { - todo!(); + Ok(Visit::Continue) } fn def_shared_b(&mut self, name: &str) -> Result { - todo!(); + self.begin_section(MappedSection::Def(name.to_string())) } fn def_shared_e(&mut self) -> Result { - todo!(); + if let (MappedSection::Def(name), nodes) = self.end_section()? { + self.sub_trees.insert(name, nodes); + Ok(Visit::Continue) + } else { + Err(anyhow!("unexpected ")) + } } fn device_b(&mut self, d: &Device) -> Result { - todo!(); + self.current_dev = Some(DeviceDetail { + mapped_blocks: d.mapped_blocks, + transaction_id: d.transaction, + creation_time: d.creation_time as u32, + snapshotted_time: d.snap_time as u32, + }); + self.begin_section(MappedSection::Dev(d.dev_id)) } fn device_e(&mut self) -> Result { - todo!(); + if let Some(detail) = self.current_dev.take() { + if let (MappedSection::Dev(thin_id), nodes) = self.end_section()? { + self.result.devices.insert(thin_id, (detail, nodes)); + Ok(Visit::Continue) + } else { + Err(anyhow!("internal error, couldn't find device details")) + } + } else { + Err(anyhow!("unexpected ")) + } } fn map(&mut self, m: &Map) -> Result { - todo!(); + if let Some((_name, _builder)) = self.map.as_mut() { + for i in 0..m.len { + let bt = BlockTime { + block: m.data_begin + i, + time: m.time, + }; + let (_, builder) = self.map.as_mut().unwrap(); + builder.push_value(self.w, m.thin_begin + i, bt)?; + } + Ok(Visit::Continue) + } else { + let msg = format!("Mapping tags must appear within a or tag."); + Err(anyhow!(msg)) + } } fn ref_shared(&mut self, name: &str) -> Result { - todo!(); + if self.current_dev.is_none() { + return Err(anyhow!( + " tags may only occur within sections." + )); + } + + if let Some(leaves) = self.sub_trees.get(name) { + // We could be in a or + if let Some((_name, builder)) = self.map.as_mut() { + builder.push_nodes(self.w, leaves)?; + } else { + let msg = format!( + " tag must be within either a or section", + name + ); + return Err(anyhow!(msg)); + } + Ok(Visit::Continue) + } else { + let msg = format!("Couldn't find sub tree '{}'.", name); + Err(anyhow!(msg)) + } } fn eof(&mut self) -> Result { - todo!(); + // FIXME: build the rest of the device trees + Ok(Visit::Continue) } } +//------------------------------------------ +/* +/// Writes a data space map to disk. Returns the space map root that needs +/// to be written to the superblock. +fn build_data_sm(batcher: WriteBatcher, sm: Box) -> Result> { + +} +*/ + //------------------------------------------ pub struct ThinRestoreOptions<'a> { @@ -66,6 +207,29 @@ pub struct ThinRestoreOptions<'a> { pub report: Arc, } +struct Context { + report: Arc, + engine: Arc, +} + +const MAX_CONCURRENT_IO: u32 = 1024; + +fn new_context(opts: &ThinRestoreOptions) -> Result { + let engine: Arc; + + if opts.async_io { + engine = Arc::new(AsyncIoEngine::new(opts.output, MAX_CONCURRENT_IO, true)?); + } else { + let nr_threads = std::cmp::max(8, num_cpus::get() * 2); + engine = Arc::new(SyncIoEngine::new(opts.output, nr_threads, true)?); + } + + Ok(Context { + report: opts.report.clone(), + engine, + }) +} + //------------------------------------------ pub fn restore(opts: ThinRestoreOptions) -> Result<()> { @@ -74,8 +238,66 @@ pub fn restore(opts: ThinRestoreOptions) -> Result<()> { .write(false) .open(opts.input)?; - let mut pass = Pass1::default(); + let ctx = new_context(&opts)?; + let max_count = u32::MAX; + + let sm = core_sm(ctx.engine.get_nr_blocks(), max_count); + let mut w = WriteBatcher::new(ctx.engine.clone(), sm.clone(), ctx.engine.get_batch_size()); + let mut pass = Pass1::new(&mut w); xml::read(input, &mut pass)?; + let pass = pass.get_result(); + + // Build the device details tree. + let mut details_builder: Builder = Builder::new(Box::new(NoopRC {})); + for (thin_id, (detail, _)) in &pass.devices { + details_builder.push_value(&mut w, *thin_id as u64, *detail)?; + } + let details_root = details_builder.complete(&mut w)?; + + // Build the individual mapping trees that make up the bottom layer. + let mut devs: BTreeMap = BTreeMap::new(); + for (thin_id, (_, nodes)) in &pass.devices { + ctx.report + .info(&format!("building btree for device {}", thin_id)); + let mut builder: Builder = Builder::new(Box::new(NoopRC {})); + builder.push_leaves(&mut w, nodes)?; + let root = builder.complete(&mut w)?; + devs.insert(*thin_id, root); + } + + // Build the top level mapping tree + let mut builder: Builder = Builder::new(Box::new(NoopRC {})); + for (thin_id, root) in devs { + builder.push_value(&mut w, thin_id as u64, root)?; + } + let mapping_root = builder.complete(&mut w)?; + + // Build data space map + + // FIXME: I think we need to decrement the shared leaves + // Build metadata space map + + // Write the superblock + if let Some(xml_sb) = pass.sb { + let sb = superblock::Superblock { + flags: SuperblockFlags { needs_check: false }, + block: SUPERBLOCK_LOCATION, + version: 2, + time: xml_sb.time as u32, + transaction_id: xml_sb.transaction, + metadata_snap: 0, + data_sm_root: vec![0; SPACE_MAP_ROOT_SIZE], + metadata_sm_root: vec![0; SPACE_MAP_ROOT_SIZE], + mapping_root, + details_root, + data_block_size: xml_sb.data_block_size, + nr_metadata_blocks: ctx.engine.get_nr_blocks(), + }; + + write_superblock(ctx.engine.as_ref(), SUPERBLOCK_LOCATION, &sb)?; + } else { + return Err(anyhow!("No superblock found in xml file")); + } Ok(()) } diff --git a/src/thin/superblock.rs b/src/thin/superblock.rs index 73e5b82..3b29b5c 100644 --- a/src/thin/superblock.rs +++ b/src/thin/superblock.rs @@ -1,10 +1,18 @@ -use crate::io_engine::*; use anyhow::{anyhow, Result}; +use byteorder::{LittleEndian, WriteBytesExt}; use nom::{bytes::complete::*, number::complete::*, IResult}; use std::fmt; +use std::io::Cursor; + +use crate::io_engine::*; +use crate::checksum::*; + +//---------------------------------------- + +pub const MAGIC: u64 = 27022010; pub const SUPERBLOCK_LOCATION: u64 = 0; -//const UUID_SIZE: usize = 16; -const SPACE_MAP_ROOT_SIZE: usize = 128; +const UUID_SIZE: usize = 16; +pub const SPACE_MAP_ROOT_SIZE: usize = 128; #[derive(Debug, Clone)] pub struct SuperblockFlags { @@ -35,36 +43,9 @@ pub struct Superblock { pub mapping_root: u64, pub details_root: u64, pub data_block_size: u32, + pub nr_metadata_blocks: u64, } -/* -pub enum CheckSeverity { - Fatal, - NonFatal, -} - -pub trait CheckError { - fn severity(&self) -> CheckSeverity; - fn block(&self) -> u64; - fn sub_errors(&self) -> Vec>; -} - -enum ErrorType { - BadChecksum, - BadBlockType(&'static str), - BadBlock(u64), - BadVersion(u32), - MetadataSnapOutOfBounds(u64), - MappingRootOutOfBounds(u64), - DetailsRootOutOfBounds(u64), -} - -struct SuperblockError { - severity: CheckSeverity, - kind: ErrorType, -} -*/ - fn unpack(data: &[u8]) -> IResult<&[u8], Superblock> { let (i, _csum) = le_u32(data)?; let (i, flags) = le_u32(i)?; @@ -81,7 +62,7 @@ fn unpack(data: &[u8]) -> IResult<&[u8], Superblock> { let (i, details_root) = le_u64(i)?; let (i, data_block_size) = le_u32(i)?; let (i, _metadata_block_size) = le_u32(i)?; - let (i, _metadata_nr_blocks) = le_u64(i)?; + let (i, nr_metadata_blocks) = le_u64(i)?; Ok(( i, @@ -100,6 +81,7 @@ fn unpack(data: &[u8]) -> IResult<&[u8], Superblock> { mapping_root, details_root, data_block_size, + nr_metadata_blocks, }, )) } @@ -115,3 +97,51 @@ pub fn read_superblock(engine: &dyn IoEngine, loc: u64) -> Result { } //------------------------------ + +fn pack_superblock(sb: &Superblock, w: &mut W) -> Result<()> { + // checksum, which we don't know yet + w.write_u32::(0)?; + + // flags + if sb.flags.needs_check { + w.write_u32::(0x1)?; + } else { + w.write_u32::(0)?; + } + + w.write_u64::(sb.block)?; + w.write_all(&vec![0; UUID_SIZE])?; + w.write_u64::(MAGIC)?; + w.write_u32::(sb.version)?; + w.write_u32::(sb.time)?; + w.write_u64::(sb.transaction_id)?; + w.write_u64::(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_u64::(sb.mapping_root)?; + w.write_u64::(sb.details_root)?; + w.write_u32::(sb.data_block_size)?; + w.write_u32::(BLOCK_SIZE as u32)?; + w.write_u64::(sb.nr_metadata_blocks)?; + + Ok(()) +} + +pub fn write_superblock(engine: &dyn IoEngine, _loc: u64, sb: &Superblock) -> Result<()> { + let b = Block::zeroed(SUPERBLOCK_LOCATION); + + // pack the superblock + { + let mut cursor = Cursor::new(b.get_data()); + pack_superblock(sb, &mut cursor)?; + } + + // calculate the checksum + write_checksum(b.get_data(), BT::SUPERBLOCK)?; + + // write + engine.write(&b)?; + Ok(()) +} + +//------------------------------ diff --git a/src/thin/xml.rs b/src/thin/xml.rs index 752dd8e..a15df16 100644 --- a/src/thin/xml.rs +++ b/src/thin/xml.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use std::{borrow::Cow, fmt::Display, io::prelude::*, io::BufReader, io::Write}; use quick_xml::events::attributes::Attribute; @@ -46,9 +46,11 @@ pub trait MetadataVisitor { fn superblock_b(&mut self, sb: &Superblock) -> Result; fn superblock_e(&mut self) -> Result; + // Defines a shared sub tree. May only contain a 'map' (no 'ref' allowed). fn def_shared_b(&mut self, name: &str) -> Result; fn def_shared_e(&mut self) -> Result; + // A device contains a number of 'map' or 'ref' items. fn device_b(&mut self, d: &Device) -> Result; fn device_e(&mut self) -> Result; @@ -207,8 +209,9 @@ fn bad_attr(_tag: &str, _attr: &[u8]) -> Result { todo!(); } -fn missing_attr(_tag: &str, _attr: &str) -> Result { - todo!(); +fn missing_attr(tag: &str, attr: &str) -> Result { + let msg = format!("missing attribute '{}' for tag '{}", attr, tag); + Err(anyhow!(msg)) } fn check_attr(tag: &str, name: &str, maybe_v: Option) -> Result { @@ -257,6 +260,24 @@ fn parse_superblock(e: &BytesStart) -> Result { }) } +fn parse_def(e: &BytesStart, tag: &str) -> Result { + let mut name: Option = None; + + for a in e.attributes() { + let kv = a.unwrap(); + match kv.key { + b"name" => { + name = Some(string_val(&kv)); + }, + _ => { + return bad_attr(tag, kv.key) + } + } + } + + Ok(name.unwrap()) +} + fn parse_device(e: &BytesStart) -> Result { let mut dev_id: Option = None; let mut mapped_blocks: Option = None; @@ -348,16 +369,19 @@ where Ok(Event::Start(ref e)) => match e.name() { b"superblock" => visitor.superblock_b(&parse_superblock(e)?), b"device" => visitor.device_b(&parse_device(e)?), + b"def" => visitor.def_shared_b(&parse_def(e, "def")?), _ => todo!(), }, Ok(Event::End(ref e)) => match e.name() { b"superblock" => visitor.superblock_e(), b"device" => visitor.device_e(), + b"def" => visitor.def_shared_e(), _ => todo!(), }, Ok(Event::Empty(ref e)) => match e.name() { b"single_mapping" => visitor.map(&parse_single_map(e)?), b"range_mapping" => visitor.map(&parse_range_map(e)?), + b"ref" => visitor.ref_shared(&parse_def(e, "ref")?), _ => todo!(), }, Ok(Event::Text(_)) => Ok(Visit::Continue), diff --git a/src/write_batcher.rs b/src/write_batcher.rs index f6ef877..f046cc8 100644 --- a/src/write_batcher.rs +++ b/src/write_batcher.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Result}; +use std::collections::BTreeSet; use std::sync::{Arc, Mutex}; use crate::checksum; @@ -10,10 +11,13 @@ use crate::pdata::space_map::*; #[derive(Clone)] pub struct WriteBatcher { pub engine: Arc, + + // FIXME: this doesn't need to be in a mutex pub sm: Arc>, batch_size: usize, queue: Vec, + allocations: BTreeSet, } impl WriteBatcher { @@ -27,10 +31,11 @@ impl WriteBatcher { sm, batch_size, queue: Vec::with_capacity(batch_size), + allocations: BTreeSet::new(), } } - pub fn alloc(&mut self) -> Result { + pub fn alloc(&mut self) -> Result { let mut sm = self.sm.lock().unwrap(); let b = sm.alloc()?; @@ -38,23 +43,37 @@ impl WriteBatcher { return Err(anyhow!("out of metadata space")); } - Ok(b.unwrap()) + Ok(Block::new(b.unwrap())) + } + + pub fn clear_allocations(&mut self) -> BTreeSet { + let mut tmp = BTreeSet::new(); + std::mem::swap(&mut tmp, &mut self.allocations); + tmp } pub fn write(&mut self, b: Block, kind: checksum::BT) -> Result<()> { checksum::write_checksum(&mut b.get_data(), kind)?; if self.queue.len() == self.batch_size { - self.flush()?; + let mut tmp = Vec::new(); + std::mem::swap(&mut tmp, &mut self.queue); + self.flush_(tmp)?; } self.queue.push(b); Ok(()) } + pub fn flush_(&mut self, queue: Vec) -> Result<()> { + self.engine.write_many(&queue)?; + Ok(()) + } + pub fn flush(&mut self) -> Result<()> { - self.engine.write_many(&self.queue)?; - self.queue.clear(); + let mut tmp = Vec::new(); + std::mem::swap(&mut tmp, &mut self.queue); + self.flush_(tmp)?; Ok(()) } } From 8bfe7ee6f3fa89482a09d3c6ea933759f4d4b4a7 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Thu, 25 Mar 2021 15:06:52 +0800 Subject: [PATCH 06/11] [btree] Fix rebalancing checks --- persistent-data/data-structures/btree.tcc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistent-data/data-structures/btree.tcc b/persistent-data/data-structures/btree.tcc index 059ebaa..27c9adc 100644 --- a/persistent-data/data-structures/btree.tcc +++ b/persistent-data/data-structures/btree.tcc @@ -338,7 +338,7 @@ namespace persistent_data { unsigned nr_right = rhs.get_nr_entries(); unsigned max_entries = get_max_entries(); - if (nr_left - count > max_entries || nr_right - count > max_entries) + if (nr_left - count > max_entries || nr_right + count > max_entries) throw runtime_error("too many entries"); if (count > 0) { From c496e8a4c85b3036455c47ff0600db18b07e3105 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Thu, 25 Mar 2021 16:42:36 +0800 Subject: [PATCH 07/11] [btree] Remove FIXMEs --- persistent-data/data-structures/btree-remove.tcc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/persistent-data/data-structures/btree-remove.tcc b/persistent-data/data-structures/btree-remove.tcc index 852d267..eeb3796 100644 --- a/persistent-data/data-structures/btree-remove.tcc +++ b/persistent-data/data-structures/btree-remove.tcc @@ -145,11 +145,11 @@ namespace persistent_data { { internal_node n = spine.get_node(); + // compact the path if there's only one child if (n.get_nr_entries() == 1) { block_address b = n.value_at(0); read_ref child = tm_.read_lock(b, validator_); - // FIXME: is it safe? ::memcpy(n.raw(), child.data(), read_ref::BLOCK_SIZE); tm_.get_sm()->dec(child.get_location()); @@ -341,7 +341,6 @@ namespace persistent_data { if (nr_left < nr_right) { int s = nr_left - target_left; - // FIXME: signed & unsigned comparison if (s < 0 && nr_center < static_cast(-s)) { // not enough in central node left.move_entries(center, -nr_center); From c41b2e8ec31e7554d1cf0a5a08038de7664098b1 Mon Sep 17 00:00:00 2001 From: Joe Thornber Date: Thu, 8 Apr 2021 10:45:50 +0100 Subject: [PATCH 08/11] Some interface changes to let dm-unit use the thin checking code more easily. --- src/bin/thin_check.rs | 22 +++--- src/report.rs | 4 +- src/thin/check.rs | 170 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 159 insertions(+), 37 deletions(-) diff --git a/src/bin/thin_check.rs b/src/bin/thin_check.rs index b7fbad9..b607c58 100644 --- a/src/bin/thin_check.rs +++ b/src/bin/thin_check.rs @@ -8,8 +8,9 @@ use std::process; use std::process::exit; use std::sync::Arc; use thinp::file_utils; +use thinp::io_engine::*; use thinp::report::*; -use thinp::thin::check::{check, ThinCheckOptions}; +use thinp::thin::check::{check, ThinCheckOptions, MAX_CONCURRENT_IO}; fn main() { let parser = App::new("thin_check") @@ -97,25 +98,22 @@ fn main() { report = Arc::new(mk_simple_report()); } - if matches.is_present("SYNC_IO") && - matches.is_present("ASYNC_IO") { + if matches.is_present("SYNC_IO") && matches.is_present("ASYNC_IO") { eprintln!("--sync-io and --async-io may not be used at the same time."); process::exit(1); } - let mut async_io = false; - if matches.is_present("ASYNC_IO") { - async_io = true; - } + let engine: Arc; - // redundant since sync is the default. - if matches.is_present("SYNC_IO") { - async_io = false; + if matches.is_present("ASYNC_IO") { + 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")); } let opts = ThinCheckOptions { - dev: &input_file, - async_io, + 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"), diff --git a/src/report.rs b/src/report.rs index e105fd5..399cad3 100644 --- a/src/report.rs +++ b/src/report.rs @@ -29,7 +29,7 @@ pub struct Report { inner: Mutex>, } -trait ReportInner { +pub trait ReportInner { fn set_title(&mut self, txt: &str); fn set_sub_title(&mut self, txt: &str); fn progress(&mut self, percent: u8); @@ -38,7 +38,7 @@ trait ReportInner { } impl Report { - fn new(inner: Box) -> Report { + pub fn new(inner: Box) -> Report { Report { outcome: Mutex::new(Success), inner: Mutex::new(inner), diff --git a/src/thin/check.rs b/src/thin/check.rs index cb25257..ea6e6ad 100644 --- a/src/thin/check.rs +++ b/src/thin/check.rs @@ -1,14 +1,13 @@ use anyhow::{anyhow, Result}; use std::collections::BTreeMap; use std::io::Cursor; -use std::path::Path; 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::{AsyncIoEngine, IoEngine, SyncIoEngine}; +use crate::io_engine::IoEngine; use crate::pdata::btree::{self, *}; use crate::pdata::btree_walker::*; use crate::pdata::space_map::*; @@ -285,11 +284,10 @@ fn inc_superblock(sm: &ASpaceMap) -> Result<()> { //------------------------------------------ -const MAX_CONCURRENT_IO: u32 = 1024; +pub const MAX_CONCURRENT_IO: u32 = 1024; -pub struct ThinCheckOptions<'a> { - pub dev: &'a Path, - pub async_io: bool, +pub struct ThinCheckOptions { + pub engine: Arc, pub sb_only: bool, pub skip_mappings: bool, pub ignore_non_fatal: bool, @@ -400,25 +398,12 @@ fn check_mapping_bottom_level( } } -fn mk_context(opts: &ThinCheckOptions) -> Result { - let engine: Arc; - let nr_threads; - - if opts.async_io { - nr_threads = std::cmp::min(4, num_cpus::get()); - engine = Arc::new(AsyncIoEngine::new( - opts.dev, - MAX_CONCURRENT_IO, - opts.auto_repair, - )?); - } else { - nr_threads = std::cmp::max(8, num_cpus::get() * 2); - engine = Arc::new(SyncIoEngine::new(opts.dev, nr_threads, opts.auto_repair)?); - } +fn mk_context(engine: Arc, report: Arc) -> Result { + let nr_threads = std::cmp::max(8, num_cpus::get() * 2); let pool = ThreadPool::new(nr_threads); Ok(Context { - report: opts.report.clone(), + report, engine, pool, }) @@ -437,7 +422,7 @@ fn bail_out(ctx: &Context, task: &str) -> Result<()> { } pub fn check(opts: ThinCheckOptions) -> Result<()> { - let ctx = mk_context(&opts)?; + let ctx = mk_context(opts.engine.clone(), opts.report.clone())?; // FIXME: temporarily get these out let report = &ctx.report; @@ -590,3 +575,142 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { } //------------------------------------------ + +// Some callers wish to know which blocks are allocated. +pub struct CheckMaps { + pub metadata_sm: Arc>, + pub data_sm: Arc>, +} + +pub fn check_with_maps(engine: Arc, report: Arc) -> Result { + let ctx = mk_context(engine.clone(), report.clone())?; + report.set_title("Checking thin metadata"); + + // superblock + let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?; + + report.info(&format!("TRANSACTION_ID={}", sb.transaction_id)); + + let metadata_root = unpack::(&sb.metadata_sm_root[0..])?; + let mut path = Vec::new(); + path.push(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::( + &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)?; + + report.set_sub_title("device details tree"); + let _devs = btree_to_map_with_sm::( + &mut path, + engine.clone(), + metadata_sm.clone(), + false, + sb.details_root, + )?; + + let (tid, stop_progress) = spawn_progress_thread( + metadata_sm.clone(), + metadata_root.nr_allocated, + report.clone(), + )?; + + // mapping top level + report.set_sub_title("mapping tree"); + let roots = btree_to_map_with_path::( + &mut path, + engine.clone(), + metadata_sm.clone(), + false, + sb.mapping_root, + )?; + + // mapping bottom level + let root = unpack::(&sb.data_sm_root[0..])?; + let data_sm = core_sm(root.nr_blocks, nr_devs as u32); + 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::(&sb.data_sm_root[0..])?; + + let entries = btree_to_map_with_sm::( + &mut path, + engine.clone(), + metadata_sm.clone(), + false, + root.bitmap_root, + )?; + let entries: Vec = 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::(&sb.metadata_sm_root[0..])?; + report.info(&format!( + "METADATA_FREE_BLOCKS={}", + 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::(b.get_data())?.indexes; + + // Unused entries will point to block 0 + let entries: Vec = 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::( + &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, + )?; + bail_out(&ctx, "metadata space map")?; + + stop_progress.store(true, Ordering::Relaxed); + tid.join().unwrap(); + + Ok(CheckMaps { + metadata_sm: metadata_sm.clone(), + data_sm: data_sm.clone(), + }) +} + + From e9899ac610e63b55867d5037f8488285ac3c3678 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Mon, 26 Apr 2021 12:47:05 +0800 Subject: [PATCH 09/11] Add missing math.rs --- src/math.rs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/math.rs diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 0000000..a3901b3 --- /dev/null +++ b/src/math.rs @@ -0,0 +1,7 @@ +pub fn div_up(v: usize, divisor: usize) -> usize { + v / divisor + (v % divisor != 0) as usize +} + +pub fn div_down(v: usize, divisor: usize) -> usize { + v / divisor +} From 4b4584c830330fc3de76dc31991c4f01c828886b Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Sun, 2 May 2021 23:51:56 +0800 Subject: [PATCH 10/11] [thin_restore (rust)] Apply several fixes - Fix reading queued blocks - Fix unnecessary block shadowing when there's no remaining values - Prevent superblock from overwritten - Flush queued writes before updating superblock --- src/pdata/btree_builder.rs | 18 +++++++++--------- src/thin/restore.rs | 3 +++ src/write_batcher.rs | 12 ++++++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/pdata/btree_builder.rs b/src/pdata/btree_builder.rs index cbae889..d11e9f4 100644 --- a/src/pdata/btree_builder.rs +++ b/src/pdata/btree_builder.rs @@ -152,7 +152,7 @@ pub trait NodeIO { fn write(&self, w: &mut WriteBatcher, keys: Vec, values: Vec) -> Result; fn read( &self, - engine: &Arc, + w: &mut WriteBatcher, block: u64, ) -> Result<(Vec, Vec)>; } @@ -180,10 +180,10 @@ impl NodeIO for LeafIO { fn read( &self, - engine: &Arc, + w: &mut WriteBatcher, block: u64, ) -> Result<(Vec, Vec)> { - let b = engine.read(block)?; + let b = w.read(block)?; let path = Vec::new(); match unpack_node::(&path, b.get_data(), true, true)? { Node::Internal { .. } => { @@ -217,10 +217,10 @@ impl NodeIO for InternalIO { fn read( &self, - engine: &Arc, + w: &mut WriteBatcher, block: u64, ) -> Result<(Vec, Vec)> { - let b = engine.read(block)?; + let b = w.read(block)?; let path = Vec::new(); match unpack_node::(&path, b.get_data(), true, true)? { Node::Internal { keys, values, .. } => Ok((keys, values)), @@ -307,7 +307,7 @@ impl<'a, V: Pack + Unpack + Clone> NodeBuilder { } // Decide if we're going to use the pre-built nodes. - if self.values.len() < half_full { + if (self.values.len() > 0) && (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)?; @@ -345,7 +345,7 @@ impl<'a, V: Pack + Unpack + Clone> NodeBuilder { pub fn complete(mut self, w: &mut WriteBatcher) -> Result> { let half_full = self.max_entries_per_node / 2; - if (self.nodes.len() > 0) && (self.values.len() < half_full) { + if (self.values.len() > 0) && (self.values.len() < half_full) && (self.nodes.len() > 0) { // 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)?; @@ -364,8 +364,8 @@ impl<'a, V: Pack + Unpack + Clone> NodeBuilder { // We're only interested in the keys and values from the node, and // not whether it's a leaf or internal node. - fn read_node(&self, w: &WriteBatcher, block: u64) -> Result<(Vec, Vec)> { - self.nio.read(&w.engine, block) + fn read_node(&self, w: &mut WriteBatcher, block: u64) -> Result<(Vec, Vec)> { + self.nio.read(w, block) } /// Writes a node with the first 'nr_entries' values. diff --git a/src/thin/restore.rs b/src/thin/restore.rs index 9094b02..e6a0a15 100644 --- a/src/thin/restore.rs +++ b/src/thin/restore.rs @@ -98,6 +98,7 @@ impl<'a> Pass1<'a> { impl<'a> MetadataVisitor for Pass1<'a> { fn superblock_b(&mut self, sb: &xml::Superblock) -> Result { self.result.sb = Some(sb.clone()); + self.w.alloc()?; Ok(Visit::Continue) } @@ -277,6 +278,8 @@ pub fn restore(opts: ThinRestoreOptions) -> Result<()> { // FIXME: I think we need to decrement the shared leaves // Build metadata space map + w.flush()?; + // Write the superblock if let Some(xml_sb) = pass.sb { let sb = superblock::Superblock { diff --git a/src/write_batcher.rs b/src/write_batcher.rs index f046cc8..3256b91 100644 --- a/src/write_batcher.rs +++ b/src/write_batcher.rs @@ -65,6 +65,18 @@ impl WriteBatcher { Ok(()) } + pub fn read(&mut self, blocknr: u64) -> Result { + for b in self.queue.iter().rev() { + if b.loc == blocknr { + let r = Block::new(b.loc); + r.get_data().copy_from_slice(b.get_data()); + return Ok(r); + } + } + + self.engine.read(blocknr).map_err(|_| anyhow!("read block error")) + } + pub fn flush_(&mut self, queue: Vec) -> Result<()> { self.engine.write_many(&queue)?; Ok(()) From 43e433149b4aaedd99ea9b3f83260ca480a55eb5 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Tue, 4 May 2021 16:10:20 +0800 Subject: [PATCH 11/11] [all] Apply cargo fmt --- src/bin/thin_check.rs | 3 +- src/cache/check.rs | 16 +++------ src/cache/hint.rs | 2 +- src/cache/mapping.rs | 3 +- src/cache/xml.rs | 23 ++++++------ src/checksum.rs | 6 ++-- src/pack/delta_list.rs | 12 ++----- src/pdata/array.rs | 7 +--- src/pdata/array_block.rs | 14 +++----- src/pdata/array_walker.rs | 23 ++++++------ src/pdata/btree_builder.rs | 51 +++++++++------------------ src/pdata/mod.rs | 3 +- src/pdata/space_map_disk.rs | 10 +++--- src/pdata/unpack.rs | 2 +- src/report.rs | 4 +-- src/shrink/copier.rs | 10 ++++-- src/thin/dump.rs | 4 +-- src/thin/superblock.rs | 10 +++--- src/thin/xml.rs | 6 ++-- src/write_batcher.rs | 4 ++- tests/cache_check.rs | 6 ++-- tests/common/cache_xml_generator.rs | 2 +- tests/common/mod.rs | 12 ++++--- tests/thin_delta.rs | 3 +- tests/thin_dump.rs | 54 +++++++++++++++++++++++------ tests/thin_metadata_pack.rs | 2 +- tests/thin_metadata_unpack.rs | 2 +- tests/thin_repair.rs | 23 ++++++++---- tests/thin_restore.rs | 2 +- tests/thin_rmap.rs | 13 +++++-- tests/thin_shrink.rs | 6 ++-- 31 files changed, 178 insertions(+), 160 deletions(-) diff --git a/src/bin/thin_check.rs b/src/bin/thin_check.rs index b7fbad9..f353f86 100644 --- a/src/bin/thin_check.rs +++ b/src/bin/thin_check.rs @@ -97,8 +97,7 @@ fn main() { report = Arc::new(mk_simple_report()); } - if matches.is_present("SYNC_IO") && - matches.is_present("ASYNC_IO") { + if matches.is_present("SYNC_IO") && matches.is_present("ASYNC_IO") { eprintln!("--sync-io and --async-io may not be used at the same time."); process::exit(1); } diff --git a/src/cache/check.rs b/src/cache/check.rs index 92895e1..265b505 100644 --- a/src/cache/check.rs +++ b/src/cache/check.rs @@ -1,13 +1,13 @@ use anyhow::anyhow; +use std::collections::*; use std::marker::PhantomData; use std::path::Path; use std::sync::{Arc, Mutex}; -use std::collections::*; -use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine}; use crate::cache::hint::*; use crate::cache::mapping::*; use crate::cache::superblock::*; +use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine}; use crate::pdata::array_walker::*; //------------------------------------------ @@ -112,19 +112,13 @@ fn mk_context(opts: &CacheCheckOptions) -> anyhow::Result { let engine: Arc; if opts.async_io { - engine = Arc::new(AsyncIoEngine::new( - opts.dev, - MAX_CONCURRENT_IO, - false, - )?); + 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, - }) + Ok(Context { engine }) } pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> { @@ -145,7 +139,7 @@ pub fn check(opts: CacheCheckOptions) -> anyhow::Result<()> { w.walk(c, sb.mapping_root)?; if sb.version >= 2 { - // TODO: check dirty bitset + // TODO: check dirty bitset } } diff --git a/src/cache/hint.rs b/src/cache/hint.rs index ef0d83f..ea7f2d3 100644 --- a/src/cache/hint.rs +++ b/src/cache/hint.rs @@ -1,6 +1,6 @@ use nom::IResult; -use std::marker::PhantomData; use std::convert::TryInto; +use std::marker::PhantomData; use crate::pdata::unpack::*; diff --git a/src/cache/mapping.rs b/src/cache/mapping.rs index c18325a..4370937 100644 --- a/src/cache/mapping.rs +++ b/src/cache/mapping.rs @@ -1,5 +1,5 @@ -use nom::IResult; use nom::number::complete::*; +use nom::IResult; use crate::pdata::unpack::*; @@ -26,7 +26,6 @@ impl Mapping { } } - impl Unpack for Mapping { fn disk_size() -> u32 { 8 diff --git a/src/cache/xml.rs b/src/cache/xml.rs index d39b057..e811b67 100644 --- a/src/cache/xml.rs +++ b/src/cache/xml.rs @@ -100,7 +100,8 @@ impl MetadataVisitor for XmlWriter { } fn superblock_e(&mut self) -> Result { - self.w.write_event(Event::End(BytesEnd::borrowed(b"superblock")))?; + self.w + .write_event(Event::End(BytesEnd::borrowed(b"superblock")))?; Ok(Visit::Continue) } @@ -112,10 +113,11 @@ impl MetadataVisitor for XmlWriter { } fn mappings_e(&mut self) -> Result { - self.w.write_event(Event::End(BytesEnd::borrowed(b"mappings")))?; + self.w + .write_event(Event::End(BytesEnd::borrowed(b"mappings")))?; Ok(Visit::Continue) } - + fn mapping(&mut self, m: &Map) -> Result { let tag = b"map"; let mut elem = BytesStart::owned(tag.to_vec(), tag.len()); @@ -132,12 +134,13 @@ impl MetadataVisitor for XmlWriter { self.w.write_event(Event::Start(elem))?; Ok(Visit::Continue) } - + fn hints_e(&mut self) -> Result { - self.w.write_event(Event::End(BytesEnd::borrowed(b"hints")))?; + self.w + .write_event(Event::End(BytesEnd::borrowed(b"hints")))?; Ok(Visit::Continue) } - + fn hint(&mut self, h: &Hint) -> Result { let tag = b"hint"; let mut elem = BytesStart::owned(tag.to_vec(), tag.len()); @@ -153,12 +156,13 @@ impl MetadataVisitor for XmlWriter { self.w.write_event(Event::Start(elem))?; Ok(Visit::Continue) } - + fn discards_e(&mut self) -> Result { - self.w.write_event(Event::End(BytesEnd::borrowed(b"discards")))?; + self.w + .write_event(Event::End(BytesEnd::borrowed(b"discards")))?; Ok(Visit::Continue) } - + fn discard(&mut self, d: &Discard) -> Result { let tag = b"discard"; let mut elem = BytesStart::owned(tag.to_vec(), tag.len()); @@ -172,4 +176,3 @@ impl MetadataVisitor for XmlWriter { Ok(Visit::Continue) } } - diff --git a/src/checksum.rs b/src/checksum.rs index edb5e0a..675e6da 100644 --- a/src/checksum.rs +++ b/src/checksum.rs @@ -55,9 +55,11 @@ pub fn write_checksum(buf: &mut [u8], kind: BT) -> Result<()> { NODE => BTREE_CSUM_XOR, BITMAP => BITMAP_CSUM_XOR, INDEX => INDEX_CSUM_XOR, - UNKNOWN => {return Err(anyhow!("Invalid block type"));} + UNKNOWN => { + return Err(anyhow!("Invalid block type")); + } }; - + let csum = checksum(buf) ^ salt; let mut out = std::io::Cursor::new(buf); out.write_u32::(csum)?; diff --git a/src/pack/delta_list.rs b/src/pack/delta_list.rs index bd14330..882e57d 100644 --- a/src/pack/delta_list.rs +++ b/src/pack/delta_list.rs @@ -12,7 +12,7 @@ use Delta::*; pub fn to_delta(ns: &[u64]) -> Vec { use std::cmp::Ordering::*; - + let mut ds = Vec::with_capacity(ns.len()); if !ns.is_empty() { @@ -31,10 +31,7 @@ pub fn to_delta(ns: &[u64]) -> Vec { count += 1; } count -= 1; - ds.push(Neg { - delta, - count, - }); + ds.push(Neg { delta, count }); base -= delta * count; } Equal => { @@ -54,10 +51,7 @@ pub fn to_delta(ns: &[u64]) -> Vec { count += 1; } count -= 1; - ds.push(Pos { - delta, - count, - }); + ds.push(Pos { delta, count }); base += delta * count; } } diff --git a/src/pdata/array.rs b/src/pdata/array.rs index d8299c2..b9e109d 100644 --- a/src/pdata/array.rs +++ b/src/pdata/array.rs @@ -21,12 +21,7 @@ impl Unpack for ArrayBlockEntry { let (i, n) = le_u64(i)?; let block = n; - Ok(( - i, - ArrayBlockEntry { - block, - } - )) + Ok((i, ArrayBlockEntry { block })) } } diff --git a/src/pdata/array_block.rs b/src/pdata/array_block.rs index 0cf7069..50da5dd 100644 --- a/src/pdata/array_block.rs +++ b/src/pdata/array_block.rs @@ -33,7 +33,7 @@ impl Unpack for ArrayBlockHeader { max_entries, nr_entries, value_size, - blocknr + blocknr, }, )) } @@ -54,17 +54,13 @@ 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( - data: &[u8], -) -> Result> { +pub fn unpack_array_block(data: &[u8]) -> Result> { // TODO: collect errors - let (i, header) = ArrayBlockHeader::unpack(data).map_err(|_e| anyhow!("Couldn't parse header"))?; + 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, - }) + Ok(ArrayBlock { header, values }) } //------------------------------------------ diff --git a/src/pdata/array_walker.rs b/src/pdata/array_walker.rs index e8287b1..6aabc82 100644 --- a/src/pdata/array_walker.rs +++ b/src/pdata/array_walker.rs @@ -11,16 +11,12 @@ use crate::pdata::unpack::*; pub struct ArrayWalker { engine: Arc, - ignore_non_fatal: bool + ignore_non_fatal: bool, } // FIXME: define another Result type for array visiting? pub trait ArrayBlockVisitor { - fn visit( - &self, - index: u64, - v: V, - ) -> anyhow::Result<()>; + fn visit(&self, index: u64, v: V) -> anyhow::Result<()>; } struct BlockValueVisitor { @@ -39,14 +35,12 @@ impl BlockValueVisitor { } } - pub fn visit_array_block( - &self, - index: u64, - array_block: ArrayBlock, - ) { + pub fn visit_array_block(&self, index: u64, array_block: ArrayBlock) { 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(); + self.array_block_visitor + .visit(begin + u64::from(i), array_block.values[i as usize]) + .unwrap(); } } } @@ -63,7 +57,10 @@ impl NodeVisitor for BlockValueVisitor { ) -> 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 b = self + .engine + .read(values[n].block) + .map_err(|_| io_err(path))?; let array_block = unpack_array_block::(b.get_data()).map_err(|_| io_err(path))?; self.visit_array_block(index, array_block); } diff --git a/src/pdata/btree_builder.rs b/src/pdata/btree_builder.rs index d11e9f4..93242bc 100644 --- a/src/pdata/btree_builder.rs +++ b/src/pdata/btree_builder.rs @@ -24,9 +24,15 @@ pub trait RefCounter { pub struct NoopRC {} impl RefCounter for NoopRC { - fn get(&self, _v: &Value) -> Result {Ok(0)} - fn inc(&mut self, _v: &Value) -> Result<()> {Ok(())} - fn dec(&mut self, _v: &Value) -> Result<()> {Ok(())} + fn get(&self, _v: &Value) -> Result { + Ok(0) + } + fn inc(&mut self, _v: &Value) -> Result<()> { + Ok(()) + } + fn dec(&mut self, _v: &Value) -> Result<()> { + Ok(()) + } } /// Wraps a space map up to become a RefCounter. @@ -150,11 +156,7 @@ fn write_node_(w: &mut WriteBatcher, mut node: Node) -> Res /// decide if it produces internal or leaf nodes. pub trait NodeIO { fn write(&self, w: &mut WriteBatcher, keys: Vec, values: Vec) -> Result; - fn read( - &self, - w: &mut WriteBatcher, - block: u64, - ) -> Result<(Vec, Vec)>; + fn read(&self, w: &mut WriteBatcher, block: u64) -> Result<(Vec, Vec)>; } pub struct LeafIO {} @@ -178,11 +180,7 @@ impl NodeIO for LeafIO { write_node_(w, node) } - fn read( - &self, - w: &mut WriteBatcher, - block: u64, - ) -> Result<(Vec, Vec)> { + fn read(&self, w: &mut WriteBatcher, block: u64) -> Result<(Vec, Vec)> { let b = w.read(block)?; let path = Vec::new(); match unpack_node::(&path, b.get_data(), true, true)? { @@ -215,11 +213,7 @@ impl NodeIO for InternalIO { write_node_(w, node) } - fn read( - &self, - w: &mut WriteBatcher, - block: u64, - ) -> Result<(Vec, Vec)> { + fn read(&self, w: &mut WriteBatcher, block: u64) -> Result<(Vec, Vec)> { let b = w.read(block)?; let path = Vec::new(); match unpack_node::(&path, b.get_data(), true, true)? { @@ -261,10 +255,7 @@ pub struct NodeSummary { impl<'a, V: Pack + Unpack + Clone> NodeBuilder { /// Create a new NodeBuilder - pub fn new( - nio: Box>, - value_rc: Box>, - ) -> Self { + pub fn new(nio: Box>, value_rc: Box>) -> Self { NodeBuilder { nio, value_rc, @@ -356,7 +347,7 @@ impl<'a, V: Pack + Unpack + Clone> NodeBuilder { if self.nodes.len() == 0 { self.emit_empty_leaf(w)? } - + Ok(self.nodes) } @@ -460,14 +451,9 @@ pub struct Builder { } impl Builder { - pub fn new( - value_rc: Box>, - ) -> Builder { + pub fn new(value_rc: Box>) -> Builder { Builder { - leaf_builder: NodeBuilder::new( - Box::new(LeafIO {}), - value_rc, - ), + leaf_builder: NodeBuilder::new(Box::new(LeafIO {}), value_rc), } } @@ -487,9 +473,7 @@ impl Builder { while nodes.len() > 1 { let mut builder = NodeBuilder::new( Box::new(InternalIO {}), - Box::new(SMRefCounter { - sm: w.sm.clone(), - }), + Box::new(SMRefCounter { sm: w.sm.clone() }), ); for n in nodes { @@ -505,4 +489,3 @@ impl Builder { } //------------------------------------------ - diff --git a/src/pdata/mod.rs b/src/pdata/mod.rs index 70d5d39..d277f7d 100644 --- a/src/pdata/mod.rs +++ b/src/pdata/mod.rs @@ -3,10 +3,9 @@ pub mod array_block; pub mod array_walker; pub mod btree; pub mod btree_builder; -pub mod btree_merge; pub mod btree_leaf_walker; +pub mod btree_merge; pub mod btree_walker; pub mod space_map; pub mod space_map_disk; pub mod unpack; - diff --git a/src/pdata/space_map_disk.rs b/src/pdata/space_map_disk.rs index b89de29..da2301e 100644 --- a/src/pdata/space_map_disk.rs +++ b/src/pdata/space_map_disk.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; use byteorder::{LittleEndian, WriteBytesExt}; use nom::{number::complete::*, IResult}; -use std::io::Cursor; use std::collections::BTreeMap; +use std::io::Cursor; use crate::checksum; use crate::io_engine::*; @@ -299,7 +299,7 @@ pub fn write_common(w: &mut WriteBatcher, sm: &dyn SpaceMap) -> Result<(Vec Result { let (index_entries, ref_count_root) = write_common(w, sm)?; - + let mut index_builder: Builder = Builder::new(Box::new(NoopRC {})); for (i, ie) in index_entries.iter().enumerate() { index_builder.push_value(w, i as u64, *ie)?; @@ -340,7 +340,7 @@ fn adjust_counts(w: &mut WriteBatcher, ie: &IndexEntry, allocs: &[u64]) -> Resul if bitmap.entries[*a as usize] == Small(0) { nr_free -= 1; } - + bitmap.entries[*a as usize] = Small(1); } @@ -350,7 +350,7 @@ fn adjust_counts(w: &mut WriteBatcher, ie: &IndexEntry, allocs: &[u64]) -> Resul w.write(bitmap_block, checksum::BT::BITMAP)?; // Return the adjusted index entry - Ok (IndexEntry { + Ok(IndexEntry { blocknr: ie.blocknr, nr_free, none_free_before: first_free, @@ -381,7 +381,7 @@ pub fn write_metadata_sm(w: &mut WriteBatcher, sm: &dyn SpaceMap) -> Result Report { Report::new(Box::new(PBInner { - title: "".to_string(), + title: "".to_string(), bar: ProgressBar::new(100), })) } diff --git a/src/shrink/copier.rs b/src/shrink/copier.rs index 1be1f2a..7e29181 100644 --- a/src/shrink/copier.rs +++ b/src/shrink/copier.rs @@ -1,7 +1,7 @@ use anyhow::Result; use std::fs::OpenOptions; +use std::io::{Read, Seek, SeekFrom, Write}; use std::path::Path; -use std::io::{Seek, SeekFrom, Write, Read}; //use std::os::unix::fs::OpenOptionsExt; pub type Sector = u64; @@ -13,7 +13,6 @@ pub struct Region { pub len: Sector, } - fn copy_step(file: &mut W, src_byte: u64, dest_byte: u64, len: usize) -> Result<()> where W: Write + Seek + Read, @@ -38,7 +37,12 @@ where let mut written = 0; while written != len_bytes { let step = u64::min(len_bytes - written, MAX_BYTES); - copy_step(file, src_bytes + written, dest_bytes + written, step as usize)?; + copy_step( + file, + src_bytes + written, + dest_bytes + written, + step as usize, + )?; written += step; } Ok(()) diff --git a/src/thin/dump.rs b/src/thin/dump.rs index f12a57a..db89b6e 100644 --- a/src/thin/dump.rs +++ b/src/thin/dump.rs @@ -298,7 +298,7 @@ fn find_shared_nodes( } } -/* + /* // FIXME: why?!! // we're not interested in leaves (roots will get re-added later). { @@ -620,7 +620,7 @@ pub fn dump(opts: ThinDumpOptions) -> Result<()> { let sb = read_superblock(ctx.engine.as_ref(), SUPERBLOCK_LOCATION)?; let md = build_metadata(&ctx, &sb)?; -/* + /* ctx.report .set_title("Optimising metadata to improve leaf packing"); let md = optimise_metadata(md)?; diff --git a/src/thin/superblock.rs b/src/thin/superblock.rs index 3b29b5c..db7074c 100644 --- a/src/thin/superblock.rs +++ b/src/thin/superblock.rs @@ -4,8 +4,8 @@ use nom::{bytes::complete::*, number::complete::*, IResult}; use std::fmt; use std::io::Cursor; -use crate::io_engine::*; use crate::checksum::*; +use crate::io_engine::*; //---------------------------------------- @@ -116,14 +116,14 @@ fn pack_superblock(sb: &Superblock, w: &mut W) -> Result<()> { w.write_u32::(sb.time)?; w.write_u64::(sb.transaction_id)?; w.write_u64::(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(&vec![0; SPACE_MAP_ROOT_SIZE])?; // data sm root + w.write_all(&vec![0; SPACE_MAP_ROOT_SIZE])?; // metadata sm root w.write_u64::(sb.mapping_root)?; w.write_u64::(sb.details_root)?; w.write_u32::(sb.data_block_size)?; w.write_u32::(BLOCK_SIZE as u32)?; w.write_u64::(sb.nr_metadata_blocks)?; - + Ok(()) } @@ -138,7 +138,7 @@ pub fn write_superblock(engine: &dyn IoEngine, _loc: u64, sb: &Superblock) -> Re // calculate the checksum write_checksum(b.get_data(), BT::SUPERBLOCK)?; - + // write engine.write(&b)?; Ok(()) diff --git a/src/thin/xml.rs b/src/thin/xml.rs index a15df16..0cdf7b4 100644 --- a/src/thin/xml.rs +++ b/src/thin/xml.rs @@ -262,16 +262,14 @@ fn parse_superblock(e: &BytesStart) -> Result { fn parse_def(e: &BytesStart, tag: &str) -> Result { let mut name: Option = None; - + for a in e.attributes() { let kv = a.unwrap(); match kv.key { b"name" => { name = Some(string_val(&kv)); - }, - _ => { - return bad_attr(tag, kv.key) } + _ => return bad_attr(tag, kv.key), } } diff --git a/src/write_batcher.rs b/src/write_batcher.rs index 3256b91..b911be5 100644 --- a/src/write_batcher.rs +++ b/src/write_batcher.rs @@ -74,7 +74,9 @@ impl WriteBatcher { } } - self.engine.read(blocknr).map_err(|_| anyhow!("read block error")) + self.engine + .read(blocknr) + .map_err(|_| anyhow!("read block error")) } pub fn flush_(&mut self, queue: Vec) -> Result<()> { diff --git a/tests/cache_check.rs b/tests/cache_check.rs index 3fb9668..8d92e5d 100644 --- a/tests/cache_check.rs +++ b/tests/cache_check.rs @@ -1,11 +1,11 @@ use anyhow::Result; -use thinp::version::TOOLS_VERSION; use duct::cmd; +use thinp::version::TOOLS_VERSION; mod common; -use common::*; use common::test_dir::*; +use common::*; //------------------------------------------ @@ -88,7 +88,7 @@ fn failing_q() -> Result<()> { assert_eq!(output.stderr.len(), 0); Ok(()) } - + #[test] fn failing_quiet() -> Result<()> { let mut td = TestDir::new()?; diff --git a/tests/common/cache_xml_generator.rs b/tests/common/cache_xml_generator.rs index d9e558e..f7ebcdb 100644 --- a/tests/common/cache_xml_generator.rs +++ b/tests/common/cache_xml_generator.rs @@ -1,4 +1,4 @@ -use anyhow::{Result}; +use anyhow::Result; use rand::prelude::*; use std::collections::HashSet; use std::fs::OpenOptions; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b139090..f9caffd 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -4,14 +4,14 @@ use anyhow::Result; use duct::{cmd, Expression}; use std::fs::OpenOptions; use std::io::{Read, Write}; -use std::path::{PathBuf}; +use std::path::PathBuf; use std::str::from_utf8; use thinp::file_utils; use thinp::io_engine::*; -pub mod thin_xml_generator; pub mod cache_xml_generator; pub mod test_dir; +pub mod thin_xml_generator; use crate::common::thin_xml_generator::{write_xml, SingleThinS}; use test_dir::TestDir; @@ -273,7 +273,12 @@ pub fn set_needs_check(md: &PathBuf) -> Result<()> { Ok(()) } -pub fn generate_metadata_leaks(md: &PathBuf, nr_blocks: u64, expected: u32, actual: u32) -> Result<()> { +pub fn generate_metadata_leaks( + md: &PathBuf, + nr_blocks: u64, + expected: u32, + actual: u32, +) -> Result<()> { let output = thin_generate_damage!( "-o", &md, @@ -318,4 +323,3 @@ where assert_eq!(csum, md5(p)?); Ok(()) } - diff --git a/tests/thin_delta.rs b/tests/thin_delta.rs index 2bc840c..458a307 100644 --- a/tests/thin_delta.rs +++ b/tests/thin_delta.rs @@ -2,8 +2,8 @@ use anyhow::Result; use thinp::version::TOOLS_VERSION; mod common; -use common::*; use common::test_dir::*; +use common::*; //------------------------------------------ @@ -68,4 +68,3 @@ fn dev_unspecified() -> Result<()> { assert!(stderr.contains("No input device provided")); Ok(()) } - diff --git a/tests/thin_dump.rs b/tests/thin_dump.rs index d2e58a9..90b4298 100644 --- a/tests/thin_dump.rs +++ b/tests/thin_dump.rs @@ -1,12 +1,12 @@ use anyhow::Result; -use thinp::file_utils; use std::fs::OpenOptions; -use std::io::{Write}; +use std::io::Write; use std::str::from_utf8; +use thinp::file_utils; mod common; -use common::*; use common::test_dir::*; +use common::*; //------------------------------------------ @@ -27,7 +27,11 @@ fn dump_restore_cycle() -> Result<()> { let output = thin_dump!(&md).run()?; let xml = td.mk_path("meta.xml"); - let mut file = OpenOptions::new().read(false).write(true).create(true).open(&xml)?; + let mut file = OpenOptions::new() + .read(false) + .write(true) + .create(true) + .open(&xml)?; file.write_all(&output.stdout[0..])?; drop(file); @@ -63,7 +67,7 @@ fn override_something(flag: &str, value: &str, pattern: &str) -> Result<()> { #[test] fn override_transaction_id() -> Result<()> { - override_something("--transaction-id", "2345", "transaction=\"2345\"") + override_something("--transaction-id", "2345", "transaction=\"2345\"") } #[test] @@ -80,13 +84,26 @@ fn override_nr_data_blocks() -> Result<()> { fn repair_superblock() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; - let before = thin_dump!("--transaction-id=5", "--data-block-size=128", "--nr-data-blocks=4096000", &md).run()?; + let before = thin_dump!( + "--transaction-id=5", + "--data-block-size=128", + "--nr-data-blocks=4096000", + &md + ) + .run()?; damage_superblock(&md)?; - let after = thin_dump!("--repair", "--transaction-id=5", "--data-block-size=128", "--nr-data-blocks=4096000", &md).run()?; + let after = thin_dump!( + "--repair", + "--transaction-id=5", + "--data-block-size=128", + "--nr-data-blocks=4096000", + &md + ) + .run()?; assert_eq!(after.stderr.len(), 0); assert_eq!(before.stdout, after.stdout); - + Ok(()) } @@ -95,7 +112,12 @@ fn missing_transaction_id() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; damage_superblock(&md)?; - let stderr = run_fail(thin_dump!("--repair", "--data-block-size=128", "--nr-data-blocks=4096000", &md))?; + let stderr = run_fail(thin_dump!( + "--repair", + "--data-block-size=128", + "--nr-data-blocks=4096000", + &md + ))?; assert!(stderr.contains("transaction id")); Ok(()) } @@ -105,7 +127,12 @@ fn missing_data_block_size() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; damage_superblock(&md)?; - let stderr = run_fail(thin_dump!("--repair", "--transaction-id=5", "--nr-data-blocks=4096000", &md))?; + let stderr = run_fail(thin_dump!( + "--repair", + "--transaction-id=5", + "--nr-data-blocks=4096000", + &md + ))?; assert!(stderr.contains("data block size")); Ok(()) } @@ -115,7 +142,12 @@ fn missing_nr_data_blocks() -> Result<()> { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; damage_superblock(&md)?; - let stderr = run_fail(thin_dump!("--repair", "--transaction-id=5", "--data-block-size=128", &md))?; + let stderr = run_fail(thin_dump!( + "--repair", + "--transaction-id=5", + "--data-block-size=128", + &md + ))?; assert!(stderr.contains("nr data blocks")); Ok(()) } diff --git a/tests/thin_metadata_pack.rs b/tests/thin_metadata_pack.rs index 4abd045..131cc95 100644 --- a/tests/thin_metadata_pack.rs +++ b/tests/thin_metadata_pack.rs @@ -2,8 +2,8 @@ use anyhow::Result; use thinp::version::TOOLS_VERSION; mod common; -use common::*; use common::test_dir::*; +use common::*; //------------------------------------------ diff --git a/tests/thin_metadata_unpack.rs b/tests/thin_metadata_unpack.rs index 26ddfce..33d3f0c 100644 --- a/tests/thin_metadata_unpack.rs +++ b/tests/thin_metadata_unpack.rs @@ -2,8 +2,8 @@ use anyhow::Result; use thinp::version::TOOLS_VERSION; mod common; -use common::*; use common::test_dir::*; +use common::*; //------------------------------------------ diff --git a/tests/thin_repair.rs b/tests/thin_repair.rs index 98ded1f..19f7969 100644 --- a/tests/thin_repair.rs +++ b/tests/thin_repair.rs @@ -3,8 +3,8 @@ use std::str::from_utf8; use thinp::version::TOOLS_VERSION; mod common; -use common::*; use common::test_dir::*; +use common::*; //------------------------------------------ @@ -132,8 +132,7 @@ fn superblock_succeeds() -> Result<()> { Ok(()) } -fn missing_thing(flag1: &str, flag2: &str, pattern: &str) -> Result<()> -{ +fn missing_thing(flag1: &str, flag2: &str, pattern: &str) -> Result<()> { let mut td = TestDir::new()?; let md1 = mk_valid_md(&mut td)?; damage_superblock(&md1)?; @@ -145,15 +144,27 @@ fn missing_thing(flag1: &str, flag2: &str, pattern: &str) -> Result<()> #[test] fn missing_transaction_id() -> Result<()> { - missing_thing("--data-block-size=128", "--nr-data-blocks=4096000", "transaction id") + missing_thing( + "--data-block-size=128", + "--nr-data-blocks=4096000", + "transaction id", + ) } #[test] fn missing_data_block_size() -> Result<()> { - missing_thing("--transaction-id=5", "--nr-data-blocks=4096000", "data block size") + missing_thing( + "--transaction-id=5", + "--nr-data-blocks=4096000", + "data block size", + ) } #[test] fn missing_nr_data_blocks() -> Result<()> { - missing_thing("--transaction-id=5", "--data-block-size=128", "nr data blocks") + missing_thing( + "--transaction-id=5", + "--data-block-size=128", + "nr data blocks", + ) } diff --git a/tests/thin_restore.rs b/tests/thin_restore.rs index 62f78dd..c8efa45 100644 --- a/tests/thin_restore.rs +++ b/tests/thin_restore.rs @@ -4,8 +4,8 @@ use thinp::file_utils; use thinp::version::TOOLS_VERSION; mod common; -use common::*; use common::test_dir::*; +use common::*; //------------------------------------------ diff --git a/tests/thin_rmap.rs b/tests/thin_rmap.rs index 1a932a2..3797edf 100644 --- a/tests/thin_rmap.rs +++ b/tests/thin_rmap.rs @@ -2,8 +2,8 @@ use anyhow::Result; use thinp::version::TOOLS_VERSION; mod common; -use common::*; use common::test_dir::*; +use common::*; //------------------------------------------ @@ -54,7 +54,16 @@ fn valid_region_format_should_pass() -> Result<()> { #[test] fn invalid_regions_should_fail() -> Result<()> { - let invalid_regions = ["23,7890", "23..six", "found..7890", "89..88", "89..89", "89..", "", "89...99"]; + let invalid_regions = [ + "23,7890", + "23..six", + "found..7890", + "89..88", + "89..89", + "89..", + "", + "89...99", + ]; for r in &invalid_regions { let mut td = TestDir::new()?; let md = mk_valid_md(&mut td)?; diff --git a/tests/thin_shrink.rs b/tests/thin_shrink.rs index 6bed874..e796dfd 100644 --- a/tests/thin_shrink.rs +++ b/tests/thin_shrink.rs @@ -3,16 +3,14 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use rand::prelude::*; use std::fs::OpenOptions; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; -use std::path::{Path}; +use std::path::Path; use thinp::file_utils; use thinp::thin::xml::{self, Visit}; mod common; use common::test_dir::*; -use common::thin_xml_generator::{ - write_xml, EmptyPoolS, FragmentedS, SingleThinS, SnapS, XmlGen -}; +use common::thin_xml_generator::{write_xml, EmptyPoolS, FragmentedS, SingleThinS, SnapS, XmlGen}; //------------------------------------