From 1368227a71ebbf8a52560f3e27d9f71c106ad9a5 Mon Sep 17 00:00:00 2001 From: Joe Thornber Date: Mon, 3 Aug 2020 15:04:59 +0100 Subject: [PATCH] [thin_check (rust)] add btree node checks --- src/pdata/btree.rs | 141 ++++++++++++++++++++++++++++++++++----------- src/thin/check.rs | 15 +++-- 2 files changed, 116 insertions(+), 40 deletions(-) diff --git a/src/pdata/btree.rs b/src/pdata/btree.rs index c19d89b..90d9733 100644 --- a/src/pdata/btree.rs +++ b/src/pdata/btree.rs @@ -10,9 +10,13 @@ use crate::checksum; pub trait ValueType { type Value; + // The size of the value when on disk. + fn disk_size() -> u32; fn unpack(data: &[u8]) -> IResult<&[u8], Self::Value>; } +const NODE_HEADER_SIZE: usize = 32; + pub struct NodeHeader { is_leaf: bool, block: u64, @@ -59,42 +63,90 @@ pub enum Node { }, } -pub fn unpack_node_(data: &[u8]) -> IResult<&[u8], Node> { - use nom::multi::count; +pub fn node_err(msg: String) -> Result { + let msg = format!("btree node error: {}", msg); + Err(anyhow!(msg)) +} - let (i, header) = unpack_node_header(data)?; - let (i, keys) = count(le_u64, header.nr_entries as usize)(i)?; - let nr_free = header.max_entries - header.nr_entries; - let (i, _padding) = count(le_u64, nr_free as usize)(i)?; - - if header.is_leaf { - let (i, values) = count(V::unpack, header.nr_entries as usize)(i)?; - Ok(( - i, - Node::Leaf { - header, - keys, - values, - }, - )) +pub fn to_any<'a, V>(r: IResult<&'a [u8], V>) -> Result<(&'a [u8], V)> { + if let Ok((i, v)) = r { + Ok((i, v)) } else { - let (i, values) = count(le_u64, header.nr_entries as usize)(i)?; - Ok(( - i, - Node::Internal { - header, - keys, - values, - }, - )) + Err(anyhow!("btree node error: parse error")) } } -pub fn unpack_node(data: &[u8]) -> Result> { - if let Ok((_i, node)) = unpack_node_(data) { - Ok(node) +pub fn unpack_node( + data: &[u8], + ignore_non_fatal: bool, + is_root: bool, +) -> Result> { + use nom::multi::count; + + let (i, header) = to_any(unpack_node_header(data))?; + + // FIXME: lift checks to own fn + if header.value_size != V::disk_size() { + return node_err(format!( + "value_size mismatch: expected {}, was {}", + V::disk_size(), + header.value_size + )); + } + + let elt_size = V::disk_size() + 8; + if elt_size as usize * header.max_entries as usize + NODE_HEADER_SIZE > BLOCK_SIZE { + return node_err(format!("max_entries is too large ({})", header.max_entries)); + } + + if header.nr_entries > header.max_entries { + return node_err(format!("nr_entries > max_entries")); + } + + if !ignore_non_fatal { + if header.max_entries % 3 != 0 { + return node_err(format!("max_entries is not divisible by 3")); + } + + if !is_root { + let min = header.max_entries / 3; + if header.nr_entries < min { + return node_err(format!("too few entries")); + } + } + } + + let (i, keys) = to_any(count(le_u64, header.nr_entries as usize)(i))?; + + let mut last = None; + for k in &keys { + if let Some(l) = last { + if k <= l { + return node_err(format!("keys out of order")); + } + } + + last = Some(k); + } + + let nr_free = header.max_entries - header.nr_entries; + let (i, _padding) = to_any(count(le_u64, nr_free as usize)(i))?; + + if header.is_leaf { + let (_i, values) = to_any(count(V::unpack, header.nr_entries as usize)(i))?; + + Ok(Node::Leaf { + header, + keys, + values, + }) } else { - Err(anyhow!("couldn't unpack btree node")) + let (_i, values) = to_any(count(le_u64, header.nr_entries as usize)(i))?; + Ok(Node::Internal { + header, + keys, + values, + }) } } @@ -104,6 +156,11 @@ pub struct ValueU64; impl ValueType for ValueU64 { type Value = u64; + + fn disk_size() -> u32 { + 8 + } + fn unpack(i: &[u8]) -> IResult<&[u8], u64> { le_u64(i) } @@ -119,19 +176,21 @@ pub trait NodeVisitor { pub struct BTreeWalker { pub engine: Arc, pub seen: Arc>, + ignore_non_fatal: bool, } impl BTreeWalker { - pub fn new(engine: AsyncIoEngine) -> BTreeWalker { + pub fn new(engine: AsyncIoEngine, ignore_non_fatal: bool) -> BTreeWalker { let nr_blocks = engine.get_nr_blocks() as usize; let r: BTreeWalker = BTreeWalker { engine: Arc::new(engine), seen: Arc::new(Mutex::new(FixedBitSet::with_capacity(nr_blocks))), + ignore_non_fatal, }; r } - pub fn walk_nodes(&mut self, visitor: &mut NV, bs: &Vec) -> Result<()> + fn walk_nodes(&mut self, visitor: &mut NV, bs: &Vec) -> Result<()> where NV: NodeVisitor, V: ValueType, @@ -148,13 +207,13 @@ impl BTreeWalker { self.engine.read_many(&mut blocks)?; for b in blocks { - self.walk_node(visitor, &b)?; + self.walk_node(visitor, &b, false)?; } Ok(()) } - pub fn walk_node(&mut self, visitor: &mut NV, b: &Block) -> Result<()> + fn walk_node(&mut self, visitor: &mut NV, b: &Block, is_root: bool) -> Result<()> where NV: NodeVisitor, V: ValueType, @@ -168,7 +227,7 @@ impl BTreeWalker { return Err(anyhow!("checksum failed for node {}, {:?}", b.loc, bt)); } - let node = unpack_node::(&b.get_data())?; + let node = unpack_node::(&b.get_data(), self.ignore_non_fatal, is_root)?; visitor.visit(self, &b, &node)?; if let Node::Internal { @@ -182,6 +241,18 @@ impl BTreeWalker { Ok(()) } + + pub fn walk( + &mut self, + visitor: &mut NV, + root: &Block, + ) -> Result<()> + where + NV: NodeVisitor, + V: ValueType, + { + self.walk_node(visitor, &root, true) + } } //------------------------------------------ diff --git a/src/thin/check.rs b/src/thin/check.rs index 46bcfcc..4946c30 100644 --- a/src/thin/check.rs +++ b/src/thin/check.rs @@ -6,8 +6,8 @@ use std::time::Instant; use threadpool::ThreadPool; use crate::block_manager::{AsyncIoEngine, Block, IoEngine}; +use crate::pdata::btree::{BTreeWalker, Node, NodeVisitor, ValueType, ValueU64}; use crate::thin::superblock::*; -use crate::pdata::btree::{ValueType, Node, BTreeWalker, NodeVisitor, ValueU64}; //------------------------------------------ @@ -21,6 +21,11 @@ struct ValueBlockTime; impl ValueType for ValueBlockTime { type Value = BlockTime; + + fn disk_size() -> u32 { + 8 + } + fn unpack(i: &[u8]) -> IResult<&[u8], BlockTime> { let (i, n) = le_u64(i)?; let block = n >> 24; @@ -72,8 +77,8 @@ impl NodeVisitor for TopLevelVisitor { let mut w = w.clone(); pool.execute(move || { let mut v = BottomLevelVisitor {}; - w.walk_node(&mut v, &b).expect("walk failed"); // FIXME: return error - eprintln!("checked thin_dev {}", thin_id); + let result = w.walk(&mut v, &b).expect("walk failed"); // FIXME: return error + eprintln!("checked thin_dev {} -> {:?}", thin_id, result); }); } @@ -105,9 +110,9 @@ pub fn check(dev: &Path) -> Result<()> { let mut root = Block::new(sb.mapping_root); engine.read(&mut root)?; - let mut w = BTreeWalker::new(engine); let mut visitor = TopLevelVisitor {}; - let _result = w.walk_node(&mut visitor, &root)?; + let mut w = BTreeWalker::new(engine, false); + let _result = w.walk(&mut visitor, &root)?; println!("read mapping tree in {} ms", now.elapsed().as_millis()); Ok(())