use std::cmp::Ordering;
use std::fmt;
use crate::chunk::{DiskKey, DISK_KEY_SIZE};
use crate::error::{Error, Result};
use crate::superblock::{
le32, le64, uuid_at, ChecksumType, Superblock, CSUM_SIZE, MAX_LEVEL, UUID_SIZE,
};
pub const HEADER_SIZE: usize = 101;
pub const LEAF_DATA_OFFSET: usize = HEADER_SIZE;
pub const KEY_PTR_SIZE: usize = DISK_KEY_SIZE + 8 + 8;
pub const ITEM_SIZE: usize = DISK_KEY_SIZE + 4 + 4;
pub mod header_offsets {
pub const CSUM: usize = 0x00;
pub const FSID: usize = 0x20;
pub const BYTENR: usize = 0x30;
pub const FLAGS: usize = 0x38;
pub const CHUNK_TREE_UUID: usize = 0x40;
pub const GENERATION: usize = 0x50;
pub const OWNER: usize = 0x58;
pub const NRITEMS: usize = 0x60;
pub const LEVEL: usize = 0x64;
}
pub mod header_flags {
pub const WRITTEN: u64 = 1 << 0;
pub const RELOC: u64 = 1 << 1;
pub const BACKREF_REV_SHIFT: u32 = 56;
pub const BACKREF_REV_MASK: u64 = 0xffu64 << BACKREF_REV_SHIFT;
pub const OLD_BACKREF_REV: u8 = 0;
pub const MIXED_BACKREF_REV: u8 = 1;
}
pub fn compare_keys(a: &DiskKey, b: &DiskKey) -> Ordering {
a.objectid
.cmp(&b.objectid)
.then(a.key_type.cmp(&b.key_type))
.then(a.offset.cmp(&b.offset))
}
fn bad_block(logical: u64, msg: impl fmt::Display) -> Error {
Error::BadSuperblock(format!("tree block at {logical:#x}: {msg}"))
}
fn hex_uuid(u: &[u8; UUID_SIZE]) -> String {
let mut s = String::with_capacity(UUID_SIZE * 2);
for b in u {
s.push_str(&format!("{b:02x}"));
}
s
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Header {
pub csum: [u8; CSUM_SIZE],
pub fsid: [u8; UUID_SIZE],
pub bytenr: u64,
pub flags: u64,
pub chunk_tree_uuid: [u8; UUID_SIZE],
pub generation: u64,
pub owner: u64,
pub nritems: u32,
pub level: u8,
}
impl Header {
pub fn parse(buf: &[u8]) -> Result<Self> {
use header_offsets as o;
if buf.len() < HEADER_SIZE {
return Err(Error::BadSuperblock(format!(
"tree block header needs {HEADER_SIZE} bytes, got {}",
buf.len()
)));
}
Ok(Header {
csum: buf[o::CSUM..o::CSUM + CSUM_SIZE]
.try_into()
.expect("32 bytes"),
fsid: uuid_at(buf, o::FSID),
bytenr: le64(buf, o::BYTENR),
flags: le64(buf, o::FLAGS),
chunk_tree_uuid: uuid_at(buf, o::CHUNK_TREE_UUID),
generation: le64(buf, o::GENERATION),
owner: le64(buf, o::OWNER),
nritems: le32(buf, o::NRITEMS),
level: buf[o::LEVEL],
})
}
pub fn is_leaf(&self) -> bool {
self.level == 0
}
pub fn backref_rev(&self) -> u8 {
((self.flags & header_flags::BACKREF_REV_MASK) >> header_flags::BACKREF_REV_SHIFT) as u8
}
pub fn is_written(&self) -> bool {
self.flags & header_flags::WRITTEN != 0
}
pub fn is_reloc(&self) -> bool {
self.flags & header_flags::RELOC != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyPtr {
pub key: DiskKey,
pub blockptr: u64,
pub generation: u64,
}
impl KeyPtr {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < KEY_PTR_SIZE {
return Err(Error::BadSuperblock(format!(
"key pointer needs {KEY_PTR_SIZE} bytes, got {}",
buf.len()
)));
}
Ok(KeyPtr {
key: DiskKey::parse(buf)?,
blockptr: le64(buf, DISK_KEY_SIZE),
generation: le64(buf, DISK_KEY_SIZE + 8),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Item {
pub key: DiskKey,
pub offset: u32,
pub size: u32,
}
impl Item {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < ITEM_SIZE {
return Err(Error::BadSuperblock(format!(
"leaf item needs {ITEM_SIZE} bytes, got {}",
buf.len()
)));
}
Ok(Item {
key: DiskKey::parse(buf)?,
offset: le32(buf, DISK_KEY_SIZE),
size: le32(buf, DISK_KEY_SIZE + 4),
})
}
pub fn data_end(&self) -> usize {
self.offset as usize + self.size as usize
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Body {
Node(Vec<KeyPtr>),
Leaf(Vec<Item>),
}
impl Body {
pub fn key_ptrs(&self) -> Option<&[KeyPtr]> {
match self {
Body::Node(p) => Some(p),
Body::Leaf(_) => None,
}
}
pub fn items(&self) -> Option<&[Item]> {
match self {
Body::Leaf(i) => Some(i),
Body::Node(_) => None,
}
}
pub fn len(&self) -> usize {
match self {
Body::Node(p) => p.len(),
Body::Leaf(i) => i.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TreeGeometry {
pub nodesize: u32,
pub csum_type: ChecksumType,
pub fsid: [u8; UUID_SIZE],
}
impl TreeGeometry {
pub fn from_superblock(sb: &Superblock) -> Self {
TreeGeometry {
nodesize: sb.nodesize,
csum_type: sb.csum_type,
fsid: sb.node_uuid(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TreeBlock {
pub header: Header,
pub body: Body,
bytes: Vec<u8>,
}
impl TreeBlock {
pub fn parse(bytes: Vec<u8>, logical: u64, geom: &TreeGeometry) -> Result<Self> {
let nodesize = geom.nodesize as usize;
if bytes.len() != nodesize {
return Err(bad_block(
logical,
format!("got {} bytes for a {nodesize}-byte node", bytes.len()),
));
}
let header = Header::parse(&bytes)?;
if !geom
.csum_type
.verify(&bytes[CSUM_SIZE..nodesize], &header.csum)
{
return Err(Error::ChecksumMismatch {
what: "tree block",
offset: logical,
});
}
if header.bytenr != logical {
return Err(Error::BlockIdentityMismatch {
what: "tree block",
expected: logical,
found: header.bytenr,
});
}
if header.fsid != geom.fsid {
return Err(bad_block(
logical,
format!(
"fsid {} is not this volume's {}",
hex_uuid(&header.fsid),
hex_uuid(&geom.fsid)
),
));
}
if header.level >= MAX_LEVEL {
return Err(bad_block(
logical,
format!(
"level {} is at or above the maximum tree height {MAX_LEVEL}",
header.level
),
));
}
let nritems = header.nritems as usize;
let body = if header.is_leaf() {
Body::Leaf(parse_leaf_items(&bytes, logical, nritems, nodesize)?)
} else {
Body::Node(parse_key_ptrs(&bytes, logical, nritems, nodesize)?)
};
Ok(TreeBlock {
header,
body,
bytes,
})
}
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
pub fn is_leaf(&self) -> bool {
self.header.is_leaf()
}
pub fn item_data(&self, item: &Item) -> Option<&[u8]> {
let start = LEAF_DATA_OFFSET.checked_add(item.offset as usize)?;
let end = start.checked_add(item.size as usize)?;
self.bytes.get(start..end)
}
}
fn parse_leaf_items(
bytes: &[u8],
logical: u64,
nritems: usize,
nodesize: usize,
) -> Result<Vec<Item>> {
let array_end = nritems
.checked_mul(ITEM_SIZE)
.ok_or_else(|| bad_block(logical, format!("nritems {nritems} overflows")))?;
if LEAF_DATA_OFFSET + array_end > nodesize {
return Err(bad_block(
logical,
format!(
"{nritems} items need {} bytes but the node is only {nodesize}",
LEAF_DATA_OFFSET + array_end
),
));
}
let leaf_data_size = nodesize - LEAF_DATA_OFFSET;
let mut items: Vec<Item> = Vec::with_capacity(nritems);
let mut expected_end = leaf_data_size;
for i in 0..nritems {
let at = LEAF_DATA_OFFSET + i * ITEM_SIZE;
let item = Item::parse(&bytes[at..])
.map_err(|_| bad_block(logical, format!("item {i} is truncated at byte {at}")))?;
let end = item.data_end();
if end > leaf_data_size {
return Err(bad_block(
logical,
format!(
"item {i} data [{}, {end}) runs past the {leaf_data_size}-byte data area",
item.offset
),
));
}
if (item.offset as usize) < array_end {
return Err(bad_block(
logical,
format!(
"item {i} data starts at {}, inside the item array that ends at {array_end}",
item.offset
),
));
}
if end != expected_end {
return Err(bad_block(
logical,
format!("item {i} data ends at {end}, expected {expected_end} — items are not contiguous"),
));
}
expected_end = item.offset as usize;
if let Some(prev) = items.last() {
if compare_keys(&prev.key, &item.key) != Ordering::Less {
return Err(bad_block(
logical,
format!("item {i} key {:?} does not follow {:?}", item.key, prev.key),
));
}
}
items.push(item);
}
Ok(items)
}
fn parse_key_ptrs(
bytes: &[u8],
logical: u64,
nritems: usize,
nodesize: usize,
) -> Result<Vec<KeyPtr>> {
if nritems == 0 {
return Err(bad_block(logical, "internal node has no children"));
}
let array_end = nritems
.checked_mul(KEY_PTR_SIZE)
.ok_or_else(|| bad_block(logical, format!("nritems {nritems} overflows")))?;
if HEADER_SIZE + array_end > nodesize {
return Err(bad_block(
logical,
format!(
"{nritems} key pointers need {} bytes but the node is only {nodesize}",
HEADER_SIZE + array_end
),
));
}
let mut ptrs: Vec<KeyPtr> = Vec::with_capacity(nritems);
for i in 0..nritems {
let at = HEADER_SIZE + i * KEY_PTR_SIZE;
let ptr = KeyPtr::parse(&bytes[at..]).map_err(|_| {
bad_block(
logical,
format!("key pointer {i} is truncated at byte {at}"),
)
})?;
if ptr.blockptr == 0 {
return Err(bad_block(logical, format!("child {i} has a null blockptr")));
}
if let Some(prev) = ptrs.last() {
if compare_keys(&prev.key, &ptr.key) != Ordering::Less {
return Err(bad_block(
logical,
format!("child {i} key {:?} does not follow {:?}", ptr.key, prev.key),
));
}
}
ptrs.push(ptr);
}
Ok(ptrs)
}
fn node_slot(ptrs: &[KeyPtr], key: &DiskKey) -> usize {
let above = ptrs.partition_point(|p| compare_keys(&p.key, key) != Ordering::Greater);
above.saturating_sub(1)
}
fn leaf_slot(items: &[Item], key: &DiskKey) -> usize {
items.partition_point(|i| compare_keys(&i.key, key) == Ordering::Less)
}
pub type CacheGet<'a> = &'a dyn Fn(u64) -> Option<TreeBlock>;
pub type CachePut<'a> = &'a dyn Fn(u64, &TreeBlock);
pub type ReadBlock<'a> = &'a dyn Fn(u64, &mut [u8]) -> Result<()>;
pub type ReadMirror<'a> = &'a dyn Fn(u64, usize, &mut [u8]) -> Result<()>;
pub type MirrorCount<'a> = &'a dyn Fn(u64) -> Result<usize>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LeafItem {
pub key: DiskKey,
pub data: Vec<u8>,
}
pub struct Tree<'a> {
geom: TreeGeometry,
read: ReadBlock<'a>,
redundancy: Option<(MirrorCount<'a>, ReadMirror<'a>)>,
cache: Option<(CacheGet<'a>, CachePut<'a>)>,
}
impl<'a> Tree<'a> {
pub fn new(geom: TreeGeometry, read: ReadBlock<'a>) -> Self {
Tree {
geom,
read,
redundancy: None,
cache: None,
}
}
pub fn with_redundancy(mut self, mirrors: MirrorCount<'a>, read: ReadMirror<'a>) -> Self {
self.redundancy = Some((mirrors, read));
self
}
pub fn with_cache(mut self, get: CacheGet<'a>, put: CachePut<'a>) -> Self {
self.cache = Some((get, put));
self
}
pub fn from_superblock(sb: &Superblock, read: ReadBlock<'a>) -> Self {
Tree::new(TreeGeometry::from_superblock(sb), read)
}
pub fn geometry(&self) -> &TreeGeometry {
&self.geom
}
pub fn read_block(&self, logical: u64) -> Result<TreeBlock> {
self.read_verified(logical, None)
}
pub fn read_child(&self, logical: u64, generation: u64) -> Result<TreeBlock> {
self.read_verified(logical, Some(generation))
}
fn read_verified(&self, logical: u64, generation: Option<u64>) -> Result<TreeBlock> {
if let Some((get, _)) = self.cache {
if let Some(block) = get(logical) {
if generation.is_none_or(|want| block.header.generation == want) {
return Ok(block);
}
}
}
let block = self.read_uncached(logical, generation)?;
if let Some((_, put)) = self.cache {
put(logical, &block);
}
Ok(block)
}
fn read_uncached(&self, logical: u64, generation: Option<u64>) -> Result<TreeBlock> {
let parse = |buf: Vec<u8>| {
let block = TreeBlock::parse(buf, logical, &self.geom)?;
match generation {
Some(want) if block.header.generation != want => Err(bad_block(
logical,
format!(
"generation {} where the parent said {want} — parent transid verify failed",
block.header.generation
),
)),
_ => Ok(block),
}
};
let mut buf = vec![0u8; self.geom.nodesize as usize];
let first = match (self.read)(logical, &mut buf) {
Ok(()) => match parse(buf) {
Ok(block) => return Ok(block),
Err(e) => e,
},
Err(e) => e,
};
let Some((mirrors, read_mirror)) = self.redundancy else {
return Err(first);
};
let Ok(count) = mirrors(logical) else {
return Err(first);
};
for mirror in 1..count {
let mut buf = vec![0u8; self.geom.nodesize as usize];
if read_mirror(logical, mirror, &mut buf).is_err() {
continue;
}
if let Ok(block) = parse(buf) {
return Ok(block);
}
}
Err(first)
}
pub fn descend(&self, root: u64, key: &DiskKey) -> Result<TreeBlock> {
let mut logical = root;
let mut expected_level: Option<u8> = None;
let mut expected_generation: Option<u64> = None;
for _ in 0..MAX_LEVEL {
let block = self.read_verified(logical, expected_generation)?;
if let Some(want) = expected_level {
if block.header.level != want {
return Err(bad_block(
logical,
format!(
"level {} where the parent said {want} — the tree is inconsistent",
block.header.level
),
));
}
}
let Some(ptrs) = block.body.key_ptrs() else {
return Ok(block);
};
let slot = node_slot(ptrs, key);
expected_level = Some(block.header.level - 1);
expected_generation = Some(ptrs[slot].generation);
logical = ptrs[slot].blockptr;
}
Err(bad_block(
root,
format!("descent did not reach a leaf within {MAX_LEVEL} levels"),
))
}
pub fn search(&self, root: u64, key: &DiskKey) -> Result<Option<LeafItem>> {
let leaf = self.descend(root, key)?;
let items = leaf
.body
.items()
.ok_or_else(|| bad_block(root, "descent ended on an internal node"))?;
let slot = leaf_slot(items, key);
let Some(item) = items.get(slot) else {
return Ok(None);
};
if compare_keys(&item.key, key) != Ordering::Equal {
return Ok(None);
}
let data = leaf
.item_data(item)
.ok_or_else(|| bad_block(leaf.header.bytenr, "item data is out of bounds"))?;
Ok(Some(LeafItem {
key: item.key,
data: data.to_vec(),
}))
}
pub fn find_all(&self, root: u64, objectid: u64, key_type: u8) -> Result<Vec<LeafItem>> {
let start = DiskKey {
objectid,
key_type,
offset: 0,
};
let mut out = Vec::new();
self.for_each_from(root, &start, &mut |key, data| {
if key.objectid != objectid || key.key_type != key_type {
return Ok(false);
}
out.push(LeafItem {
key: *key,
data: data.to_vec(),
});
Ok(true)
})?;
Ok(out)
}
pub fn for_each(
&self,
root: u64,
visit: &mut dyn FnMut(&DiskKey, &[u8]) -> Result<bool>,
) -> Result<()> {
self.walk(root, None, &mut WalkState::new(), visit)
.map(|_| ())
}
pub fn for_each_from(
&self,
root: u64,
start: &DiskKey,
visit: &mut dyn FnMut(&DiskKey, &[u8]) -> Result<bool>,
) -> Result<()> {
self.walk(root, Some(start), &mut WalkState::new(), visit)
.map(|_| ())
}
fn walk(
&self,
logical: u64,
start: Option<&DiskKey>,
state: &mut WalkState,
visit: &mut dyn FnMut(&DiskKey, &[u8]) -> Result<bool>,
) -> Result<bool> {
if state.depth >= MAX_LEVEL {
return Err(bad_block(
logical,
format!("walk went deeper than {MAX_LEVEL} levels"),
));
}
if !state.seen.insert(logical) {
return Err(bad_block(
logical,
"block appears twice in one walk — the tree is not a tree",
));
}
let block = self.read_verified(logical, state.expected_generation)?;
if let Some(want) = state.expected_level {
if block.header.level != want {
return Err(bad_block(
logical,
format!(
"level {} where the parent said {want} — the tree is inconsistent",
block.header.level
),
));
}
}
match &block.body {
Body::Leaf(items) => {
let first = start.map_or(0, |k| leaf_slot(items, k));
for item in items.iter().skip(first) {
let data = block.item_data(item).ok_or_else(|| {
bad_block(block.header.bytenr, "item data is out of bounds")
})?;
if !visit(&item.key, data)? {
return Ok(false);
}
}
Ok(true)
}
Body::Node(ptrs) => {
let first = start.map_or(0, |k| node_slot(ptrs, k));
let child_level = block
.header
.level
.checked_sub(1)
.ok_or_else(|| bad_block(logical, "a node at level 0 has children"))?;
for (i, ptr) in ptrs.iter().enumerate().skip(first) {
let child_start = if i == first { start } else { None };
let outer = state.enter(child_level, ptr.generation);
let keep_going = self.walk(ptr.blockptr, child_start, state, visit);
state.leave(outer);
if !keep_going? {
return Ok(false);
}
}
Ok(true)
}
}
}
}
struct WalkState {
seen: std::collections::HashSet<u64>,
depth: u8,
expected_level: Option<u8>,
expected_generation: Option<u64>,
}
impl WalkState {
fn new() -> Self {
Self {
seen: std::collections::HashSet::new(),
depth: 0,
expected_level: None,
expected_generation: None,
}
}
fn enter(&mut self, level: u8, generation: u64) -> (Option<u8>, Option<u64>) {
let outer = (self.expected_level, self.expected_generation);
self.expected_level = Some(level);
self.expected_generation = Some(generation);
self.depth += 1;
outer
}
fn leave(&mut self, outer: (Option<u8>, Option<u64>)) {
(self.expected_level, self.expected_generation) = outer;
self.depth -= 1;
}
}
#[cfg(test)]
pub(crate) mod test_blocks {
use super::*;
pub const NODESIZE: u32 = 4096;
pub const FSID: [u8; UUID_SIZE] = [0x5A; UUID_SIZE];
pub const CSUM: ChecksumType = ChecksumType::Crc32c;
pub const LEAF_A: u64 = 0x1000;
pub const LEAF_B: u64 = 0x2000;
pub const ROOT: u64 = 0x3000;
pub fn geom() -> TreeGeometry {
TreeGeometry {
nodesize: NODESIZE,
csum_type: CSUM,
fsid: FSID,
}
}
pub fn key(objectid: u64, key_type: u8, offset: u64) -> DiskKey {
DiskKey {
objectid,
key_type,
offset,
}
}
pub fn put_key(b: &mut [u8], at: usize, k: &DiskKey) {
b[at..at + 8].copy_from_slice(&k.objectid.to_le_bytes());
b[at + 8] = k.key_type;
b[at + 9..at + 17].copy_from_slice(&k.offset.to_le_bytes());
}
pub fn put_header(b: &mut [u8], bytenr: u64, owner: u64, nritems: u32, level: u8) {
use header_offsets as o;
b[o::FSID..o::FSID + UUID_SIZE].copy_from_slice(&FSID);
b[o::BYTENR..o::BYTENR + 8].copy_from_slice(&bytenr.to_le_bytes());
let flags = (u64::from(header_flags::MIXED_BACKREF_REV) << header_flags::BACKREF_REV_SHIFT)
| header_flags::WRITTEN;
b[o::FLAGS..o::FLAGS + 8].copy_from_slice(&flags.to_le_bytes());
b[o::CHUNK_TREE_UUID..o::CHUNK_TREE_UUID + UUID_SIZE].copy_from_slice(&[0xC4; UUID_SIZE]);
b[o::GENERATION..o::GENERATION + 8].copy_from_slice(&9u64.to_le_bytes());
b[o::OWNER..o::OWNER + 8].copy_from_slice(&owner.to_le_bytes());
b[o::NRITEMS..o::NRITEMS + 4].copy_from_slice(&nritems.to_le_bytes());
b[o::LEVEL] = level;
}
pub fn seal(b: &mut [u8]) {
let digest = CSUM.digest(&b[CSUM_SIZE..]);
b[..CSUM_SIZE].copy_from_slice(&digest);
}
pub fn leaf(bytenr: u64, owner: u64, entries: &[(DiskKey, Vec<u8>)]) -> Vec<u8> {
let mut b = vec![0u8; NODESIZE as usize];
put_header(&mut b, bytenr, owner, entries.len() as u32, 0);
let mut end = NODESIZE as usize - LEAF_DATA_OFFSET;
for (i, (k, data)) in entries.iter().enumerate() {
let offset = end - data.len();
let at = LEAF_DATA_OFFSET + i * ITEM_SIZE;
put_key(&mut b, at, k);
b[at + DISK_KEY_SIZE..at + DISK_KEY_SIZE + 4]
.copy_from_slice(&(offset as u32).to_le_bytes());
b[at + DISK_KEY_SIZE + 4..at + DISK_KEY_SIZE + 8]
.copy_from_slice(&(data.len() as u32).to_le_bytes());
let start = LEAF_DATA_OFFSET + offset;
b[start..start + data.len()].copy_from_slice(data);
end = offset;
}
seal(&mut b);
b
}
pub fn node(bytenr: u64, owner: u64, level: u8, children: &[(DiskKey, u64)]) -> Vec<u8> {
let mut b = vec![0u8; NODESIZE as usize];
put_header(&mut b, bytenr, owner, children.len() as u32, level);
for (i, (k, child)) in children.iter().enumerate() {
let at = HEADER_SIZE + i * KEY_PTR_SIZE;
put_key(&mut b, at, k);
b[at + DISK_KEY_SIZE..at + DISK_KEY_SIZE + 8].copy_from_slice(&child.to_le_bytes());
b[at + DISK_KEY_SIZE + 8..at + DISK_KEY_SIZE + 16].copy_from_slice(&9u64.to_le_bytes());
}
seal(&mut b);
b
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunk::objectid;
use std::collections::HashMap;
use super::test_blocks::*;
fn node(bytenr: u64, owner: u64, level: u8, children: &[(DiskKey, u64)]) -> Vec<u8> {
let mut b = vec![0u8; NODESIZE as usize];
put_header(&mut b, bytenr, owner, children.len() as u32, level);
for (i, (k, child)) in children.iter().enumerate() {
let at = HEADER_SIZE + i * KEY_PTR_SIZE;
put_key(&mut b, at, k);
b[at + DISK_KEY_SIZE..at + DISK_KEY_SIZE + 8].copy_from_slice(&child.to_le_bytes());
b[at + DISK_KEY_SIZE + 8..at + DISK_KEY_SIZE + 16].copy_from_slice(&9u64.to_le_bytes());
}
seal(&mut b);
b
}
fn two_level_tree() -> HashMap<u64, Vec<u8>> {
let a = leaf(
LEAF_A,
objectid::FS_TREE,
&[
(key(1, 1, 0), b"a".to_vec()),
(key(2, 1, 0), b"bb".to_vec()),
],
);
let b = leaf(
LEAF_B,
objectid::FS_TREE,
&[
(key(5, 1, 0), b"ccc".to_vec()),
(key(9, 1, 0), b"dddd".to_vec()),
],
);
let r = node(
ROOT,
objectid::FS_TREE,
1,
&[(key(1, 1, 0), LEAF_A), (key(5, 1, 0), LEAF_B)],
);
HashMap::from([(LEAF_A, a), (LEAF_B, b), (ROOT, r)])
}
fn reader(blocks: &HashMap<u64, Vec<u8>>) -> impl Fn(u64, &mut [u8]) -> Result<()> + '_ {
move |logical, buf| match blocks.get(&logical) {
Some(block) => {
buf.copy_from_slice(block);
Ok(())
}
None => Err(Error::Io(format!("no block at {logical:#x}"))),
}
}
fn collect(tree: &Tree<'_>, root: u64) -> Vec<(DiskKey, Vec<u8>)> {
let mut out = Vec::new();
tree.for_each(root, &mut |k, d| {
out.push((*k, d.to_vec()));
Ok(true)
})
.unwrap();
out
}
#[test]
fn struct_sizes_match_the_on_disk_layout() {
assert_eq!(HEADER_SIZE, 32 + 16 + 8 + 8 + 16 + 8 + 8 + 4 + 1);
assert_eq!(HEADER_SIZE, header_offsets::LEVEL + 1);
assert_eq!(KEY_PTR_SIZE, 17 + 8 + 8);
assert_eq!(ITEM_SIZE, 17 + 4 + 4);
assert_eq!(header_offsets::CSUM, crate::superblock::offsets::CSUM);
assert_eq!(header_offsets::FSID, crate::superblock::offsets::FSID);
assert_eq!(header_offsets::BYTENR, crate::superblock::offsets::BYTENR);
}
#[test]
fn parses_and_verifies_a_leaf_header() {
let bytes = leaf(
LEAF_A,
objectid::CHUNK_TREE,
&[(key(7, 3, 11), vec![0xAB; 6])],
);
let block = TreeBlock::parse(bytes, LEAF_A, &geom()).unwrap();
assert_eq!(block.header.bytenr, LEAF_A);
assert_eq!(block.header.owner, objectid::CHUNK_TREE);
assert_eq!(block.header.nritems, 1);
assert_eq!(block.header.level, 0);
assert!(block.header.is_leaf());
assert!(block.header.is_written());
assert!(!block.header.is_reloc());
assert_eq!(block.header.backref_rev(), header_flags::MIXED_BACKREF_REV);
let items = block.body.items().unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items[0].key, key(7, 3, 11));
assert_eq!(block.item_data(&items[0]).unwrap(), &[0xAB; 6]);
}
#[test]
fn parses_an_internal_node() {
let bytes = node(
ROOT,
objectid::ROOT_TREE,
2,
&[(key(1, 1, 0), LEAF_A), (key(5, 1, 0), LEAF_B)],
);
let block = TreeBlock::parse(bytes, ROOT, &geom()).unwrap();
assert!(!block.is_leaf());
assert_eq!(block.header.level, 2);
let ptrs = block.body.key_ptrs().unwrap();
assert_eq!(ptrs.len(), 2);
assert_eq!(ptrs[1].blockptr, LEAF_B);
assert_eq!(ptrs[1].generation, 9);
assert!(block.body.items().is_none());
assert_eq!(block.body.len(), 2);
assert!(!block.body.is_empty());
}
#[test]
fn rejects_a_corrupted_block() {
let mut bytes = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), vec![1, 2, 3])]);
bytes[HEADER_SIZE + 3] ^= 0xFF;
assert!(matches!(
TreeBlock::parse(bytes, LEAF_A, &geom()),
Err(Error::ChecksumMismatch {
what: "tree block",
offset: LEAF_A
})
));
}
#[test]
fn rejects_an_intact_block_read_from_the_wrong_address() {
let bytes = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), vec![1])]);
assert!(matches!(
TreeBlock::parse(bytes, LEAF_B, &geom()),
Err(Error::BlockIdentityMismatch {
what: "tree block",
expected: LEAF_B,
found: LEAF_A
})
));
}
#[test]
fn rejects_a_block_belonging_to_another_filesystem() {
let mut bytes = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), vec![1])]);
bytes[header_offsets::FSID..header_offsets::FSID + UUID_SIZE].copy_from_slice(&[0x11; 16]);
seal(&mut bytes);
let err = TreeBlock::parse(bytes, LEAF_A, &geom()).unwrap_err();
assert!(
format!("{err}").contains("is not this volume's"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_a_level_above_the_maximum_tree_height() {
let mut bytes = node(ROOT, objectid::FS_TREE, 1, &[(key(1, 1, 0), LEAF_A)]);
bytes[header_offsets::LEVEL] = MAX_LEVEL;
seal(&mut bytes);
let err = TreeBlock::parse(bytes, ROOT, &geom()).unwrap_err();
assert!(
format!("{err}").contains("maximum tree height"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_an_item_count_that_cannot_fit_in_the_block() {
let mut bytes = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), vec![1])]);
let too_many = (NODESIZE as usize / ITEM_SIZE) as u32 + 1;
bytes[header_offsets::NRITEMS..header_offsets::NRITEMS + 4]
.copy_from_slice(&too_many.to_le_bytes());
seal(&mut bytes);
let err = TreeBlock::parse(bytes, LEAF_A, &geom()).unwrap_err();
assert!(
format!("{err}").contains("but the node is only"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_item_data_running_past_the_end_of_the_leaf() {
let mut bytes = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), vec![1, 2, 3])]);
let size_at = LEAF_DATA_OFFSET + DISK_KEY_SIZE + 4;
bytes[size_at..size_at + 4].copy_from_slice(&9999u32.to_le_bytes());
seal(&mut bytes);
let err = TreeBlock::parse(bytes, LEAF_A, &geom()).unwrap_err();
assert!(
format!("{err}").contains("runs past"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_item_data_overlapping_the_item_array() {
let mut bytes = leaf(
LEAF_A,
objectid::FS_TREE,
&[(key(1, 1, 0), vec![0; 8]), (key(2, 1, 0), vec![0; 8])],
);
let at = LEAF_DATA_OFFSET + ITEM_SIZE;
bytes[at + DISK_KEY_SIZE..at + DISK_KEY_SIZE + 4].copy_from_slice(&0u32.to_le_bytes());
bytes[at + DISK_KEY_SIZE + 4..at + DISK_KEY_SIZE + 8].copy_from_slice(&8u32.to_le_bytes());
seal(&mut bytes);
let err = TreeBlock::parse(bytes, LEAF_A, &geom()).unwrap_err();
assert!(
format!("{err}").contains("inside the item array"),
"unexpected error: {err}"
);
}
#[test]
fn a_damaged_first_copy_is_read_from_the_second() {
let good = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), b"a".to_vec())]);
let mut damaged = good.clone();
damaged[NODESIZE as usize - 1] ^= 0xff;
let read = |_logical: u64, buf: &mut [u8]| -> Result<()> {
buf.copy_from_slice(&damaged);
Ok(())
};
let mirrors = |_logical: u64| -> Result<usize> { Ok(2) };
let read_mirror = |_logical: u64, mirror: usize, buf: &mut [u8]| -> Result<()> {
buf.copy_from_slice(if mirror == 0 { &damaged } else { &good });
Ok(())
};
let one_copy = Tree::new(geom(), &read);
assert!(
one_copy.read_block(LEAF_A).is_err(),
"copy 0 does not verify, and without the others there is nothing else to read"
);
let with_copies = Tree::new(geom(), &read).with_redundancy(&mirrors, &read_mirror);
let block = with_copies
.read_block(LEAF_A)
.expect("copy 1 verifies and is what the caller gets");
assert_eq!(block.header.bytenr, LEAF_A);
}
fn with_generation(block: &[u8], generation: u64) -> Vec<u8> {
use header_offsets as o;
let mut b = block.to_vec();
b[o::GENERATION..o::GENERATION + 8].copy_from_slice(&generation.to_le_bytes());
seal(&mut b);
b
}
#[test]
fn a_child_from_another_transaction_is_refused() {
let mut blocks = two_level_tree();
let stale = with_generation(&blocks[&LEAF_B], 10);
blocks.insert(LEAF_B, stale);
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
let err = tree.descend(ROOT, &key(5, 1, 0)).unwrap_err();
assert!(
format!("{err}").contains("parent transid verify failed"),
"descend: {err}"
);
let err = tree.for_each(ROOT, &mut |_, _| Ok(true)).unwrap_err();
assert!(
format!("{err}").contains("generation 10 where the parent said 9"),
"walk: {err}"
);
tree.descend(ROOT, &key(1, 1, 0))
.expect("leaf A matches its pointer");
}
#[test]
fn children_matching_their_pointers_read_even_when_older_than_the_parent() {
let mut blocks = two_level_tree();
let newer_root = with_generation(&blocks[&ROOT], 50);
blocks.insert(ROOT, newer_root);
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
let mut n = 0;
tree.for_each(ROOT, &mut |_, _| {
n += 1;
Ok(true)
})
.expect("walk");
assert_eq!(n, 4);
tree.descend(ROOT, &key(9, 1, 0)).expect("descend");
}
#[test]
fn a_stale_first_copy_is_read_from_the_second() {
let good = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), b"a".to_vec())]);
let stale = with_generation(&good, 4);
let read = |_logical: u64, buf: &mut [u8]| -> Result<()> {
buf.copy_from_slice(&stale);
Ok(())
};
let mirrors = |_logical: u64| -> Result<usize> { Ok(2) };
let read_mirror = |_logical: u64, mirror: usize, buf: &mut [u8]| -> Result<()> {
buf.copy_from_slice(if mirror == 0 { &stale } else { &good });
Ok(())
};
let tree = Tree::new(geom(), &read).with_redundancy(&mirrors, &read_mirror);
let block = tree
.read_child(LEAF_A, 9)
.expect("copy 1 carries the generation the parent recorded");
assert_eq!(block.header.generation, 9);
let err = Tree::new(geom(), &read).read_child(LEAF_A, 9).unwrap_err();
assert!(format!("{err}").contains("generation 4"), "{err}");
let all_stale = |_logical: u64, _mirror: usize, buf: &mut [u8]| -> Result<()> {
buf.copy_from_slice(&stale);
Ok(())
};
let tree = Tree::new(geom(), &read).with_redundancy(&mirrors, &all_stale);
assert!(
tree.read_child(LEAF_A, 9).is_err(),
"a stale mirror was accepted"
);
}
#[test]
fn when_no_copy_verifies_the_damage_is_what_is_reported() {
let good = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), b"a".to_vec())]);
let mut damaged = good.clone();
damaged[NODESIZE as usize - 1] ^= 0xff;
let read = |_logical: u64, buf: &mut [u8]| -> Result<()> {
buf.copy_from_slice(&damaged);
Ok(())
};
let mirrors = |_logical: u64| -> Result<usize> { Ok(2) };
let read_mirror = |logical: u64, mirror: usize, _buf: &mut [u8]| -> Result<()> {
assert_eq!(mirror, 1, "copy 0 was already read through `read`");
Err(Error::UnmappedLogical(logical))
};
let tree = Tree::new(geom(), &read).with_redundancy(&mirrors, &read_mirror);
let err = tree.read_block(LEAF_A).unwrap_err();
assert!(
!matches!(err, Error::UnmappedLogical(_)),
"the second copy's failure is not the finding: {err}"
);
}
#[test]
fn a_first_copy_that_verifies_is_not_read_again() {
let good = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), b"a".to_vec())]);
let read = |_logical: u64, buf: &mut [u8]| -> Result<()> {
buf.copy_from_slice(&good);
Ok(())
};
let asked = std::cell::Cell::new(0usize);
let mirrors = |_logical: u64| -> Result<usize> {
asked.set(asked.get() + 1);
Ok(2)
};
let read_mirror = |_logical: u64, _mirror: usize, _buf: &mut [u8]| -> Result<()> {
panic!("the first copy verified; nothing else should be read");
};
let tree = Tree::new(geom(), &read).with_redundancy(&mirrors, &read_mirror);
tree.read_block(LEAF_A).expect("the first copy verifies");
assert_eq!(
asked.get(),
0,
"a healthy read must not even ask how many copies there are"
);
}
#[test]
fn rejects_a_leaf_whose_keys_do_not_ascend() {
let bytes = leaf(
LEAF_A,
objectid::FS_TREE,
&[(key(9, 1, 0), vec![1]), (key(2, 1, 0), vec![2])],
);
let err = TreeBlock::parse(bytes, LEAF_A, &geom()).unwrap_err();
assert!(
format!("{err}").contains("does not follow"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_an_internal_node_with_no_children() {
let bytes = node(ROOT, objectid::FS_TREE, 1, &[]);
let err = TreeBlock::parse(bytes, ROOT, &geom()).unwrap_err();
assert!(
format!("{err}").contains("no children"),
"unexpected error: {err}"
);
}
#[test]
fn compares_keys_objectid_then_type_then_offset() {
assert_eq!(compare_keys(&key(1, 9, 9), &key(2, 0, 0)), Ordering::Less);
assert_eq!(compare_keys(&key(1, 1, 9), &key(1, 2, 0)), Ordering::Less);
assert_eq!(compare_keys(&key(1, 1, 1), &key(1, 1, 2)), Ordering::Less);
assert_eq!(compare_keys(&key(1, 1, 1), &key(1, 1, 1)), Ordering::Equal);
assert_eq!(
compare_keys(&key(u64::MAX, 0, 0), &key(1, 0, 0)),
Ordering::Greater
);
}
fn one_child_reached_eight_ways() -> HashMap<u64, Vec<u8>> {
const FANOUT: u64 = 8;
let leaf_block = leaf(LEAF_A, objectid::FS_TREE, &[(key(1, 1, 0), b"a".to_vec())]);
let mut blocks = HashMap::from([(LEAF_A, leaf_block)]);
let addresses = [0x6000u64, 0x5000, 0x4000, ROOT];
let mut below = LEAF_A;
for (i, &at) in addresses.iter().enumerate() {
let children: Vec<(DiskKey, u64)> = (0..FANOUT)
.map(|slot| (key(1 + slot, 1, 0), below))
.collect();
blocks.insert(at, node(at, objectid::FS_TREE, (i + 1) as u8, &children));
below = at;
}
blocks
}
#[test]
fn a_block_reached_twice_in_one_walk_is_refused() {
let blocks = one_child_reached_eight_ways();
let reads = std::cell::Cell::new(0usize);
let read = |logical: u64, buf: &mut [u8]| match blocks.get(&logical) {
Some(block) => {
reads.set(reads.get() + 1);
buf.copy_from_slice(block);
Ok(())
}
None => Err(Error::Io(format!("no block at {logical:#x}"))),
};
let tree = Tree::new(geom(), &read);
let mut visited = 0usize;
let outcome = tree.for_each(ROOT, &mut |_, _| {
visited += 1;
Ok(true)
});
assert!(
outcome.is_err(),
"a walk of a five-block DAG visited {visited} items and read \
{} blocks without objecting",
reads.get()
);
assert!(
reads.get() <= blocks.len(),
"the walk read {} blocks from an image holding {}",
reads.get(),
blocks.len()
);
}
#[test]
fn walks_a_two_level_tree_in_key_order() {
let blocks = two_level_tree();
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
let items = collect(&tree, ROOT);
assert_eq!(
items.iter().map(|(k, _)| k.objectid).collect::<Vec<_>>(),
vec![1, 2, 5, 9]
);
assert_eq!(items[3].1, b"dddd");
}
#[test]
fn finds_an_item_in_each_leaf() {
let blocks = two_level_tree();
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
assert_eq!(
tree.search(ROOT, &key(2, 1, 0)).unwrap().unwrap().data,
b"bb"
);
assert_eq!(
tree.search(ROOT, &key(9, 1, 0)).unwrap().unwrap().data,
b"dddd"
);
}
#[test]
fn reports_a_missing_key_rather_than_the_neighbouring_one() {
let blocks = two_level_tree();
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
assert!(tree.search(ROOT, &key(3, 1, 0)).unwrap().is_none());
assert!(tree.search(ROOT, &key(0, 0, 0)).unwrap().is_none());
assert!(tree.search(ROOT, &key(99, 1, 0)).unwrap().is_none());
assert!(tree.search(ROOT, &key(5, 2, 0)).unwrap().is_none());
}
#[test]
fn descends_to_the_leaf_that_would_hold_a_key() {
let blocks = two_level_tree();
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
assert_eq!(
tree.descend(ROOT, &key(2, 1, 0)).unwrap().header.bytenr,
LEAF_A
);
assert_eq!(
tree.descend(ROOT, &key(7, 1, 0)).unwrap().header.bytenr,
LEAF_B
);
assert_eq!(
tree.descend(ROOT, &key(0, 0, 0)).unwrap().header.bytenr,
LEAF_A
);
}
#[test]
fn iterating_from_a_key_skips_everything_before_it() {
let blocks = two_level_tree();
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
let mut seen = Vec::new();
tree.for_each_from(ROOT, &key(5, 1, 0), &mut |k, _| {
seen.push(k.objectid);
Ok(true)
})
.unwrap();
assert_eq!(seen, vec![5, 9]);
let mut seen = Vec::new();
tree.for_each_from(ROOT, &key(3, 0, 0), &mut |k, _| {
seen.push(k.objectid);
Ok(true)
})
.unwrap();
assert_eq!(seen, vec![5, 9]);
}
#[test]
fn a_visitor_can_stop_the_walk_early() {
let blocks = two_level_tree();
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
let mut seen = Vec::new();
tree.for_each(ROOT, &mut |k, _| {
seen.push(k.objectid);
Ok(k.objectid < 2)
})
.unwrap();
assert_eq!(seen, vec![1, 2]);
}
#[test]
fn find_all_collects_one_objectid_and_type_and_stops() {
let a = leaf(
LEAF_A,
objectid::FS_TREE,
&[
(key(4, 1, 0), b"before".to_vec()),
(key(5, 1, 0), b"first".to_vec()),
],
);
let b = leaf(
LEAF_B,
objectid::FS_TREE,
&[
(key(5, 1, 7), b"second".to_vec()),
(key(5, 2, 0), b"other type".to_vec()),
(key(6, 1, 0), b"after".to_vec()),
],
);
let r = node(
ROOT,
objectid::FS_TREE,
1,
&[(key(4, 1, 0), LEAF_A), (key(5, 1, 7), LEAF_B)],
);
let blocks = HashMap::from([(LEAF_A, a), (LEAF_B, b), (ROOT, r)]);
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
let found = tree.find_all(ROOT, 5, 1).unwrap();
assert_eq!(
found.iter().map(|i| i.key.offset).collect::<Vec<_>>(),
vec![0, 7]
);
assert_eq!(found[1].data, b"second");
assert!(tree.find_all(ROOT, 5, 3).unwrap().is_empty());
}
#[test]
fn a_child_at_the_wrong_level_stops_the_descent() {
let mut blocks = two_level_tree();
let bogus = node(LEAF_A, objectid::FS_TREE, 1, &[(key(1, 1, 0), LEAF_B)]);
blocks.insert(LEAF_A, bogus);
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
let err = tree.search(ROOT, &key(1, 1, 0)).unwrap_err();
assert!(
format!("{err}").contains("the tree is inconsistent"),
"unexpected error: {err}"
);
}
#[test]
fn a_cycle_in_a_corrupt_tree_terminates() {
let mut b = vec![0u8; NODESIZE as usize];
put_header(&mut b, ROOT, objectid::FS_TREE, 1, 7);
put_key(&mut b, HEADER_SIZE, &key(0, 0, 0));
b[HEADER_SIZE + DISK_KEY_SIZE..HEADER_SIZE + DISK_KEY_SIZE + 8]
.copy_from_slice(&ROOT.to_le_bytes());
seal(&mut b);
let blocks = HashMap::from([(ROOT, b)]);
let read = reader(&blocks);
let tree = Tree::new(geom(), &read);
assert!(tree.search(ROOT, &key(1, 1, 0)).is_err());
assert!(tree.for_each(ROOT, &mut |_, _| Ok(true)).is_err());
}
#[test]
fn a_short_read_is_rejected_rather_than_parsed() {
let bytes = vec![0u8; NODESIZE as usize - 1];
let err = TreeBlock::parse(bytes, LEAF_A, &geom()).unwrap_err();
assert!(
format!("{err}").contains("for a 4096-byte node"),
"unexpected error: {err}"
);
}
}