use btrfs_disk::{
superblock::ChecksumType,
tree::{DiskKey, KeyType, TreeBlock},
util::{csum_tree_block, write_disk_key},
};
use bytes::{Buf, BufMut};
use uuid::Uuid;
pub const HEADER_SIZE: usize = 101;
pub const ITEM_SIZE: usize = 25;
pub const KEY_PTR_SIZE: usize = 33;
pub const DISK_KEY_SIZE: usize = 17;
pub const BTRFS_MAX_LEVEL: usize = 8;
#[derive(Clone)]
pub struct ExtentBuffer {
data: Vec<u8>,
logical: u64,
}
impl ExtentBuffer {
#[must_use]
pub fn from_raw(data: Vec<u8>, logical: u64) -> Self {
assert!(!data.is_empty(), "ExtentBuffer: empty data");
Self { data, logical }
}
#[must_use]
pub fn new_zeroed(nodesize: u32, logical: u64) -> Self {
Self {
data: vec![0u8; nodesize as usize],
logical,
}
}
#[must_use]
pub fn logical(&self) -> u64 {
self.logical
}
pub fn set_logical(&mut self, logical: u64) {
self.logical = logical;
}
#[must_use]
pub fn nodesize(&self) -> u32 {
self.data.len() as u32
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
&mut self.data
}
#[must_use]
pub fn as_tree_block(&self) -> TreeBlock {
TreeBlock::parse(&self.data)
}
#[must_use]
pub fn generation(&self) -> u64 {
let mut b = &self.data[80..88];
b.get_u64_le()
}
#[must_use]
pub fn owner(&self) -> u64 {
let mut b = &self.data[88..96];
b.get_u64_le()
}
#[must_use]
pub fn nritems(&self) -> u32 {
let mut b = &self.data[96..100];
b.get_u32_le()
}
#[must_use]
pub fn level(&self) -> u8 {
self.data[100]
}
#[must_use]
pub fn bytenr(&self) -> u64 {
let mut b = &self.data[48..56];
b.get_u64_le()
}
#[must_use]
pub fn flags(&self) -> u64 {
let mut b = &self.data[56..64];
b.get_u64_le()
}
#[must_use]
pub fn fsid(&self) -> Uuid {
Uuid::from_bytes(self.data[32..48].try_into().unwrap())
}
#[must_use]
pub fn chunk_tree_uuid(&self) -> Uuid {
Uuid::from_bytes(self.data[64..80].try_into().unwrap())
}
pub fn set_generation(&mut self, generation: u64) {
(&mut self.data[80..88]).put_u64_le(generation);
}
pub fn set_owner(&mut self, owner: u64) {
(&mut self.data[88..96]).put_u64_le(owner);
}
pub fn set_nritems(&mut self, nritems: u32) {
(&mut self.data[96..100]).put_u32_le(nritems);
}
pub fn set_level(&mut self, level: u8) {
self.data[100] = level;
}
pub fn set_bytenr(&mut self, bytenr: u64) {
(&mut self.data[48..56]).put_u64_le(bytenr);
}
pub fn set_flags(&mut self, flags: u64) {
(&mut self.data[56..64]).put_u64_le(flags);
}
pub fn set_fsid(&mut self, fsid: &Uuid) {
self.data[32..48].copy_from_slice(fsid.as_bytes());
}
pub fn set_chunk_tree_uuid(&mut self, uuid: &Uuid) {
self.data[64..80].copy_from_slice(uuid.as_bytes());
}
#[must_use]
pub fn item_key(&self, slot: usize) -> DiskKey {
let off = HEADER_SIZE + slot * ITEM_SIZE;
DiskKey::parse(&self.data, off)
}
#[must_use]
pub fn item_offset(&self, slot: usize) -> u32 {
let off = HEADER_SIZE + slot * ITEM_SIZE + DISK_KEY_SIZE;
let mut b = &self.data[off..off + 4];
b.get_u32_le()
}
#[must_use]
pub fn item_size(&self, slot: usize) -> u32 {
let off = HEADER_SIZE + slot * ITEM_SIZE + DISK_KEY_SIZE + 4;
let mut b = &self.data[off..off + 4];
b.get_u32_le()
}
#[must_use]
pub fn item_data_offset(&self, slot: usize) -> usize {
HEADER_SIZE + self.item_offset(slot) as usize
}
#[must_use]
pub fn item_data(&self, slot: usize) -> &[u8] {
let start = self.item_data_offset(slot);
let size = self.item_size(slot) as usize;
&self.data[start..start + size]
}
pub fn item_data_mut(&mut self, slot: usize) -> &mut [u8] {
let start = self.item_data_offset(slot);
let size = self.item_size(slot) as usize;
&mut self.data[start..start + size]
}
pub fn set_item_key(&mut self, slot: usize, key: &DiskKey) {
let off = HEADER_SIZE + slot * ITEM_SIZE;
write_disk_key(&mut self.data, off, key);
}
pub fn set_item_offset(&mut self, slot: usize, offset: u32) {
let off = HEADER_SIZE + slot * ITEM_SIZE + DISK_KEY_SIZE;
(&mut self.data[off..off + 4]).put_u32_le(offset);
}
pub fn set_item_size(&mut self, slot: usize, size: u32) {
let off = HEADER_SIZE + slot * ITEM_SIZE + DISK_KEY_SIZE + 4;
(&mut self.data[off..off + 4]).put_u32_le(size);
}
#[must_use]
pub fn key_ptr_key(&self, slot: usize) -> DiskKey {
let off = HEADER_SIZE + slot * KEY_PTR_SIZE;
DiskKey::parse(&self.data, off)
}
#[must_use]
pub fn key_ptr_blockptr(&self, slot: usize) -> u64 {
let off = HEADER_SIZE + slot * KEY_PTR_SIZE + DISK_KEY_SIZE;
let mut b = &self.data[off..off + 8];
b.get_u64_le()
}
#[must_use]
pub fn key_ptr_generation(&self, slot: usize) -> u64 {
let off = HEADER_SIZE + slot * KEY_PTR_SIZE + DISK_KEY_SIZE + 8;
let mut b = &self.data[off..off + 8];
b.get_u64_le()
}
pub fn set_key_ptr_key(&mut self, slot: usize, key: &DiskKey) {
let off = HEADER_SIZE + slot * KEY_PTR_SIZE;
write_disk_key(&mut self.data, off, key);
}
pub fn set_key_ptr_blockptr(&mut self, slot: usize, blockptr: u64) {
let off = HEADER_SIZE + slot * KEY_PTR_SIZE + DISK_KEY_SIZE;
(&mut self.data[off..off + 8]).put_u64_le(blockptr);
}
pub fn set_key_ptr_generation(&mut self, slot: usize, generation: u64) {
let off = HEADER_SIZE + slot * KEY_PTR_SIZE + DISK_KEY_SIZE + 8;
(&mut self.data[off..off + 8]).put_u64_le(generation);
}
pub fn set_key_ptr(
&mut self,
slot: usize,
key: &DiskKey,
blockptr: u64,
generation: u64,
) {
self.set_key_ptr_key(slot, key);
self.set_key_ptr_blockptr(slot, blockptr);
self.set_key_ptr_generation(slot, generation);
}
#[must_use]
pub fn leaf_free_space(&self) -> u32 {
let nritems = self.nritems() as usize;
if nritems == 0 {
return self.nodesize() - HEADER_SIZE as u32;
}
let items_end = (HEADER_SIZE + nritems * ITEM_SIZE) as u32;
let data_start = self.leaf_data_end();
data_start.saturating_sub(items_end)
}
#[must_use]
pub fn leaf_data_end(&self) -> u32 {
let nritems = self.nritems();
if nritems == 0 {
return self.nodesize();
}
HEADER_SIZE as u32 + self.item_offset(nritems as usize - 1)
}
pub fn update_checksum(&mut self, csum_type: ChecksumType) {
csum_tree_block(&mut self.data, csum_type);
}
pub fn copy_within(&mut self, src: core::ops::Range<usize>, dest: usize) {
self.data.copy_within(src, dest);
}
pub fn zero_range(&mut self, offset: usize, len: usize) {
self.data[offset..offset + len].fill(0);
}
#[must_use]
pub fn is_leaf(&self) -> bool {
self.level() == 0
}
#[must_use]
pub fn is_node(&self) -> bool {
self.level() > 0
}
#[must_use]
pub fn max_key_ptrs(&self) -> u32 {
(self.nodesize() - HEADER_SIZE as u32) / KEY_PTR_SIZE as u32
}
pub fn check_leaf(&self) -> Result<(), String> {
if self.level() != 0 {
return Err(format!(
"check_leaf: block at {} has level {} (expected 0)",
self.logical,
self.level()
));
}
if self.bytenr() != self.logical {
return Err(format!(
"check_leaf: bytenr {} != logical {}",
self.bytenr(),
self.logical
));
}
let nritems = self.nritems() as usize;
let nodesize = self.nodesize() as usize;
let items_end = HEADER_SIZE + nritems * ITEM_SIZE;
if items_end > nodesize {
return Err(format!(
"check_leaf: {nritems} items need {items_end} bytes, \
block is only {nodesize}",
));
}
if nritems == 0 {
return Ok(());
}
for i in 1..nritems {
let prev = self.item_key(i - 1);
let curr = self.item_key(i);
if key_cmp(&prev, &curr) != std::cmp::Ordering::Less {
return Err(format!(
"check_leaf: key at slot {} ({:?}) >= key at slot {i} ({curr:?})",
i - 1,
prev,
));
}
}
let first_data_end = HEADER_SIZE
+ self.item_offset(0) as usize
+ self.item_size(0) as usize;
if first_data_end > nodesize {
return Err(format!(
"check_leaf: item 0 data ends at {first_data_end}, \
beyond block size {nodesize}",
));
}
for i in 1..nritems {
let prev_off = self.item_offset(i - 1);
let curr_off = self.item_offset(i);
let curr_size = self.item_size(i);
if curr_off > prev_off {
return Err(format!(
"check_leaf: offset at slot {i} ({curr_off}) > \
offset at slot {} ({prev_off})",
i - 1,
));
}
if curr_off + curr_size > prev_off {
return Err(format!(
"check_leaf: item {i} data [{curr_off}..{}] overlaps \
item {} data [{prev_off}..{}]",
curr_off + curr_size,
i - 1,
prev_off + self.item_size(i - 1),
));
}
}
let data_start = HEADER_SIZE + self.item_offset(nritems - 1) as usize;
if items_end > data_start {
return Err(format!(
"check_leaf: item descriptors end at {items_end} but \
data starts at {data_start}",
));
}
Ok(())
}
pub fn check_node(&self) -> Result<(), String> {
if self.level() == 0 {
return Err(format!(
"check_node: block at {} has level 0 (expected > 0)",
self.logical,
));
}
if self.bytenr() != self.logical {
return Err(format!(
"check_node: bytenr {} != logical {}",
self.bytenr(),
self.logical
));
}
let nritems = self.nritems() as usize;
let max_ptrs = self.max_key_ptrs() as usize;
if nritems > max_ptrs {
return Err(format!(
"check_node: {nritems} key pointers exceeds maximum {max_ptrs}",
));
}
for i in 0..nritems {
if self.key_ptr_blockptr(i) == 0 {
return Err(format!("check_node: slot {i} has blockptr 0"));
}
}
for i in 1..nritems {
let prev = self.key_ptr_key(i - 1);
let curr = self.key_ptr_key(i);
if key_cmp(&prev, &curr) != std::cmp::Ordering::Less {
return Err(format!(
"check_node: key at slot {} ({:?}) >= key at slot {i} ({curr:?})",
i - 1,
prev,
));
}
}
Ok(())
}
pub fn check(&self) -> Result<(), String> {
if self.level() == 0 {
self.check_leaf()
} else {
self.check_node()
}
}
}
#[inline]
pub fn debug_assert_leaf_valid(eb: &ExtentBuffer) {
debug_assert!(
eb.check_leaf().is_ok(),
"leaf invariant violation at logical {}: {}",
eb.logical(),
eb.check_leaf().unwrap_err(),
);
}
#[inline]
pub fn debug_assert_node_valid(eb: &ExtentBuffer) {
debug_assert!(
eb.check_node().is_ok(),
"node invariant violation at logical {}: {}",
eb.logical(),
eb.check_node().unwrap_err(),
);
}
#[inline]
pub fn debug_assert_block_valid(eb: &ExtentBuffer) {
debug_assert!(
eb.check().is_ok(),
"block invariant violation at logical {}: {}",
eb.logical(),
eb.check().unwrap_err(),
);
}
#[allow(clippy::missing_fields_in_debug)]
impl std::fmt::Debug for ExtentBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExtentBuffer")
.field("logical", &self.logical)
.field("level", &self.level())
.field("nritems", &self.nritems())
.field("generation", &self.generation())
.field("owner", &self.owner())
.finish()
}
}
#[must_use]
pub fn key_cmp(a: &DiskKey, b: &DiskKey) -> std::cmp::Ordering {
a.objectid
.cmp(&b.objectid)
.then_with(|| a.key_type.to_raw().cmp(&b.key_type.to_raw()))
.then_with(|| a.offset.cmp(&b.offset))
}
#[must_use]
pub fn min_key() -> DiskKey {
DiskKey {
objectid: 0,
key_type: KeyType::from_raw(0),
offset: 0,
}
}
#[must_use]
pub fn max_key() -> DiskKey {
DiskKey {
objectid: u64::MAX,
key_type: KeyType::from_raw(u8::MAX),
offset: u64::MAX,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_leaf(
nodesize: u32,
nritems: u32,
generation: u64,
owner: u64,
) -> ExtentBuffer {
let mut eb = ExtentBuffer::new_zeroed(nodesize, 65536);
eb.set_generation(generation);
eb.set_owner(owner);
eb.set_nritems(nritems);
eb.set_level(0);
eb.set_bytenr(65536);
eb
}
fn make_node(
nodesize: u32,
nritems: u32,
level: u8,
generation: u64,
) -> ExtentBuffer {
let mut eb = ExtentBuffer::new_zeroed(nodesize, 131072);
eb.set_generation(generation);
eb.set_owner(2);
eb.set_nritems(nritems);
eb.set_level(level);
eb.set_bytenr(131072);
eb
}
#[test]
fn header_round_trip() {
let mut eb = ExtentBuffer::new_zeroed(16384, 65536);
eb.set_generation(42);
eb.set_owner(5);
eb.set_nritems(10);
eb.set_level(0);
eb.set_bytenr(65536);
eb.set_flags(1);
assert_eq!(eb.generation(), 42);
assert_eq!(eb.owner(), 5);
assert_eq!(eb.nritems(), 10);
assert_eq!(eb.level(), 0);
assert_eq!(eb.bytenr(), 65536);
assert_eq!(eb.flags(), 1);
assert_eq!(eb.logical(), 65536);
assert_eq!(eb.nodesize(), 16384);
assert!(eb.is_leaf());
assert!(!eb.is_node());
}
#[test]
fn uuid_round_trip() {
let mut eb = ExtentBuffer::new_zeroed(16384, 0);
let fsid =
Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
let ctu =
Uuid::parse_str("01234567-89ab-cdef-0123-456789abcdef").unwrap();
eb.set_fsid(&fsid);
eb.set_chunk_tree_uuid(&ctu);
assert_eq!(eb.fsid(), fsid);
assert_eq!(eb.chunk_tree_uuid(), ctu);
}
#[test]
fn item_accessors() {
let mut eb = make_leaf(16384, 2, 7, 5);
let key0 = DiskKey {
objectid: 256,
key_type: KeyType::InodeItem,
offset: 0,
};
let key1 = DiskKey {
objectid: 256,
key_type: KeyType::DirItem,
offset: 100,
};
let data_off_0 = 16384 - HEADER_SIZE as u32 - 160;
eb.set_item_key(0, &key0);
eb.set_item_offset(0, data_off_0);
eb.set_item_size(0, 160);
let data_off_1 = data_off_0 - 50;
eb.set_item_key(1, &key1);
eb.set_item_offset(1, data_off_1);
eb.set_item_size(1, 50);
let k0 = eb.item_key(0);
assert_eq!(k0.objectid, 256);
assert_eq!(k0.key_type, KeyType::InodeItem);
assert_eq!(k0.offset, 0);
assert_eq!(eb.item_offset(0), data_off_0);
assert_eq!(eb.item_size(0), 160);
let k1 = eb.item_key(1);
assert_eq!(k1.objectid, 256);
assert_eq!(k1.key_type, KeyType::DirItem);
assert_eq!(k1.offset, 100);
assert_eq!(eb.item_offset(1), data_off_1);
assert_eq!(eb.item_size(1), 50);
assert_eq!(eb.item_data(0).len(), 160);
assert_eq!(eb.item_data(1).len(), 50);
eb.item_data_mut(0)[0] = 0xAA;
eb.item_data_mut(1)[0] = 0xBB;
assert_eq!(eb.item_data(0)[0], 0xAA);
assert_eq!(eb.item_data(1)[0], 0xBB);
}
#[test]
fn key_ptr_accessors() {
let mut eb = make_node(16384, 3, 1, 10);
for i in 0..3u64 {
let key = DiskKey {
objectid: i + 1,
key_type: KeyType::RootItem,
offset: 0,
};
eb.set_key_ptr(i as usize, &key, (i + 1) * 65536, 10 - i);
}
for i in 0..3u64 {
let k = eb.key_ptr_key(i as usize);
assert_eq!(k.objectid, i + 1);
assert_eq!(k.key_type, KeyType::RootItem);
assert_eq!(eb.key_ptr_blockptr(i as usize), (i + 1) * 65536);
assert_eq!(eb.key_ptr_generation(i as usize), 10 - i);
}
}
#[test]
fn leaf_free_space_empty() {
let eb = make_leaf(16384, 0, 1, 5);
assert_eq!(eb.leaf_free_space(), 16384 - HEADER_SIZE as u32);
}
#[test]
fn leaf_free_space_with_items() {
let mut eb = make_leaf(4096, 1, 1, 5);
let data_off = 4096 - HEADER_SIZE as u32 - 100;
eb.set_item_key(
0,
&DiskKey {
objectid: 1,
key_type: KeyType::InodeItem,
offset: 0,
},
);
eb.set_item_offset(0, data_off);
eb.set_item_size(0, 100);
let expected = (HEADER_SIZE as u32 + data_off)
- (HEADER_SIZE as u32 + ITEM_SIZE as u32);
assert_eq!(eb.leaf_free_space(), expected);
}
#[test]
fn key_comparison() {
let a = DiskKey {
objectid: 1,
key_type: KeyType::InodeItem,
offset: 0,
};
let b = DiskKey {
objectid: 2,
key_type: KeyType::InodeItem,
offset: 0,
};
assert_eq!(key_cmp(&a, &b), std::cmp::Ordering::Less);
assert_eq!(key_cmp(&b, &a), std::cmp::Ordering::Greater);
assert_eq!(key_cmp(&a, &a), std::cmp::Ordering::Equal);
let c = DiskKey {
objectid: 1,
key_type: KeyType::DirItem,
offset: 0,
};
assert_eq!(key_cmp(&a, &c), std::cmp::Ordering::Less);
}
#[test]
fn min_max_keys() {
assert_eq!(key_cmp(&min_key(), &max_key()), std::cmp::Ordering::Less);
assert_eq!(key_cmp(&min_key(), &min_key()), std::cmp::Ordering::Equal);
}
#[test]
fn checksum_round_trip() {
let mut eb = make_leaf(4096, 0, 1, 5);
eb.set_bytenr(65536);
eb.update_checksum(ChecksumType::Crc32);
assert_ne!(&eb.as_bytes()[0..4], &[0, 0, 0, 0]);
let csum1: [u8; 4] = eb.as_bytes()[0..4].try_into().unwrap();
eb.update_checksum(ChecksumType::Crc32);
let csum2: [u8; 4] = eb.as_bytes()[0..4].try_into().unwrap();
assert_eq!(csum1, csum2);
}
#[test]
fn checksum_dispatches_per_algorithm() {
for csum_type in [
ChecksumType::Xxhash,
ChecksumType::Sha256,
ChecksumType::Blake2,
] {
let mut eb = make_leaf(4096, 0, 1, 5);
eb.set_bytenr(65536);
eb.update_checksum(csum_type);
let n = csum_type.size();
assert!(
eb.as_bytes()[..n].iter().any(|&b| b != 0),
"{csum_type:?}: hash bytes are all zero",
);
assert!(
eb.as_bytes()[n..32].iter().all(|&b| b == 0),
"{csum_type:?}: tail of csum field is not zero",
);
}
}
#[test]
fn clone_independence() {
let mut eb = make_leaf(4096, 0, 1, 5);
let eb2 = eb.clone();
eb.set_generation(999);
assert_eq!(eb.generation(), 999);
assert_eq!(eb2.generation(), 1);
}
#[test]
fn as_tree_block_parse() {
let mut eb = make_leaf(4096, 1, 7, 5);
eb.set_bytenr(65536);
let key = DiskKey {
objectid: 256,
key_type: KeyType::InodeItem,
offset: 0,
};
let data_off = 4096 - HEADER_SIZE as u32 - 160;
eb.set_item_key(0, &key);
eb.set_item_offset(0, data_off);
eb.set_item_size(0, 160);
let tb = eb.as_tree_block();
match &tb {
TreeBlock::Leaf { header, items, .. } => {
assert_eq!(header.generation, 7);
assert_eq!(items.len(), 1);
assert_eq!(items[0].key.objectid, 256);
}
TreeBlock::Node { .. } => panic!("expected leaf"),
}
}
#[test]
fn copy_within_and_zero() {
let mut eb = ExtentBuffer::new_zeroed(256, 0);
eb.as_bytes_mut()[10] = 0xAA;
eb.as_bytes_mut()[11] = 0xBB;
eb.copy_within(10..12, 20);
assert_eq!(eb.as_bytes()[20], 0xAA);
assert_eq!(eb.as_bytes()[21], 0xBB);
eb.zero_range(20, 2);
assert_eq!(eb.as_bytes()[20], 0);
assert_eq!(eb.as_bytes()[21], 0);
}
#[test]
fn check_leaf_valid() {
let mut eb = make_leaf(4096, 0, 1, 5);
eb.set_bytenr(65536);
assert!(eb.check_leaf().is_ok());
let mut data_end = 4096u32;
let key0 = DiskKey {
objectid: 1,
key_type: KeyType::InodeItem,
offset: 0,
};
data_end -= 100;
eb.set_item_key(0, &key0);
eb.set_item_offset(0, data_end - HEADER_SIZE as u32);
eb.set_item_size(0, 100);
let key1 = DiskKey {
objectid: 2,
key_type: KeyType::InodeItem,
offset: 0,
};
data_end -= 50;
eb.set_item_key(1, &key1);
eb.set_item_offset(1, data_end - HEADER_SIZE as u32);
eb.set_item_size(1, 50);
eb.set_nritems(2);
assert!(eb.check_leaf().is_ok());
}
#[test]
fn check_leaf_bad_key_order() {
let mut eb = make_leaf(4096, 0, 1, 5);
eb.set_bytenr(65536);
eb.set_nritems(2);
let key0 = DiskKey {
objectid: 10,
key_type: KeyType::InodeItem,
offset: 0,
};
let key1 = DiskKey {
objectid: 5,
key_type: KeyType::InodeItem,
offset: 0,
};
eb.set_item_key(0, &key0);
eb.set_item_offset(0, 4096 - HEADER_SIZE as u32 - 50);
eb.set_item_size(0, 50);
eb.set_item_key(1, &key1);
eb.set_item_offset(1, 4096 - HEADER_SIZE as u32 - 100);
eb.set_item_size(1, 50);
let err = eb.check_leaf().unwrap_err();
assert!(err.contains("key at slot 0"), "{err}");
}
#[test]
fn check_leaf_bytenr_mismatch() {
let mut eb = make_leaf(4096, 0, 1, 5);
eb.set_bytenr(99999); let err = eb.check_leaf().unwrap_err();
assert!(err.contains("bytenr"), "{err}");
}
#[test]
fn check_node_valid() {
let mut eb = make_node(4096, 2, 1, 10);
let key0 = DiskKey {
objectid: 1,
key_type: KeyType::RootItem,
offset: 0,
};
let key1 = DiskKey {
objectid: 100,
key_type: KeyType::RootItem,
offset: 0,
};
eb.set_key_ptr(0, &key0, 65536, 10);
eb.set_key_ptr(1, &key1, 131072, 10);
assert!(eb.check_node().is_ok());
}
#[test]
fn check_node_bad_key_order() {
let mut eb = make_node(4096, 2, 1, 10);
let key0 = DiskKey {
objectid: 100,
key_type: KeyType::RootItem,
offset: 0,
};
let key1 = DiskKey {
objectid: 1,
key_type: KeyType::RootItem,
offset: 0,
};
eb.set_key_ptr(0, &key0, 65536, 10);
eb.set_key_ptr(1, &key1, 131072, 10);
let err = eb.check_node().unwrap_err();
assert!(err.contains("key at slot 0"), "{err}");
}
#[test]
fn check_node_zero_blockptr() {
let mut eb = make_node(4096, 1, 1, 10);
let key0 = DiskKey {
objectid: 1,
key_type: KeyType::RootItem,
offset: 0,
};
eb.set_key_ptr_key(0, &key0);
eb.set_key_ptr_blockptr(0, 0);
eb.set_key_ptr_generation(0, 10);
let err = eb.check_node().unwrap_err();
assert!(err.contains("blockptr 0"), "{err}");
}
#[test]
fn check_dispatches_by_level() {
let mut leaf = make_leaf(4096, 0, 1, 5);
leaf.set_bytenr(65536);
assert!(leaf.check().is_ok());
let mut node = make_node(4096, 1, 1, 10);
let key = DiskKey {
objectid: 1,
key_type: KeyType::RootItem,
offset: 0,
};
node.set_key_ptr(0, &key, 65536, 10);
assert!(node.check().is_ok());
}
}