use crate::btree::KeyPtr;
use crate::btree::{header_offsets as o, HEADER_SIZE, ITEM_SIZE, KEY_PTR_SIZE, LEAF_DATA_OFFSET};
use crate::chunk::{DiskKey, DISK_KEY_SIZE};
use crate::error::{Error, Result};
use crate::superblock::{Superblock, CSUM_SIZE};
#[derive(Debug, Clone)]
pub struct LeafItem<'a> {
pub key: DiskKey,
pub data: &'a [u8],
}
pub fn space_needed(items: &[LeafItem]) -> usize {
HEADER_SIZE
+ items
.iter()
.map(|i| ITEM_SIZE + i.data.len())
.sum::<usize>()
}
#[derive(Debug, Clone, Copy)]
pub struct BlockIdentity {
pub bytenr: u64,
pub owner: u64,
pub generation: u64,
pub level: u8,
pub flags: u64,
pub chunk_tree_uuid: [u8; 16],
}
pub fn flags_for_new_block() -> u64 {
use crate::btree::header_flags::{MIXED_BACKREF_REV, WRITTEN};
WRITTEN | ((MIXED_BACKREF_REV as u64) << crate::btree::header_flags::BACKREF_REV_SHIFT)
}
pub fn chunk_tree_uuid_of(block: &[u8]) -> [u8; 16] {
let mut out = [0u8; 16];
out.copy_from_slice(&block[o::CHUNK_TREE_UUID..o::CHUNK_TREE_UUID + 16]);
out
}
pub fn build_leaf(sb: &Superblock, id: BlockIdentity, items: &[LeafItem]) -> Result<Vec<u8>> {
let nodesize = sb.nodesize as usize;
if id.level != 0 {
return Err(Error::UnsupportedFeature(format!(
"a leaf is level 0 by definition and this one claims level {}; a block \
holding items at a level above the leaves is one a descent reads as \
key pointers",
id.level
)));
}
let needed = space_needed(items);
if needed > nodesize {
return Err(Error::UnsupportedFeature(format!(
"{} items need {needed} bytes and a tree block holds {nodesize}; this \
builds one block, so splitting the items is the caller's decision — \
`leaf_edit::split` divides a list and `leaf_edit::insert_or_split` \
does it as part of an insert",
items.len()
)));
}
for pair in items.windows(2) {
if !key_before(&pair[0].key, &pair[1].key) {
return Err(Error::UnsupportedFeature(format!(
"leaf items are out of order: {:?} is not before {:?}",
pair[0].key, pair[1].key
)));
}
}
let mut block = vec![0u8; nodesize];
write_header(&mut block, sb, id, items.len() as u32);
let mut data_end = nodesize;
for (i, item) in items.iter().enumerate() {
let at = LEAF_DATA_OFFSET + i * ITEM_SIZE;
data_end -= item.data.len();
write_key(&mut block[at..], &item.key);
let offset = (data_end - LEAF_DATA_OFFSET) as u32;
let k = at + DISK_KEY_SIZE;
block[k..k + 4].copy_from_slice(&offset.to_le_bytes());
block[k + 4..k + 8].copy_from_slice(&(item.data.len() as u32).to_le_bytes());
block[data_end..data_end + item.data.len()].copy_from_slice(item.data);
}
stamp_checksum(&mut block, sb);
Ok(block)
}
pub fn key_ptr_capacity(sb: &Superblock) -> usize {
(sb.nodesize as usize - HEADER_SIZE) / KEY_PTR_SIZE
}
pub fn build_node(sb: &Superblock, id: BlockIdentity, ptrs: &[KeyPtr]) -> Result<Vec<u8>> {
let nodesize = sb.nodesize as usize;
if id.level == 0 {
return Err(Error::UnsupportedFeature(
"a node holding key pointers cannot be at level 0, which is what a leaf is".to_string(),
));
}
let capacity = key_ptr_capacity(sb);
if ptrs.len() > capacity {
return Err(Error::UnsupportedFeature(format!(
"{} key pointers need more than the {capacity} a {nodesize}-byte node holds; \
splitting a node is not implemented",
ptrs.len()
)));
}
for pair in ptrs.windows(2) {
if !key_before(&pair[0].key, &pair[1].key) {
return Err(Error::UnsupportedFeature(format!(
"node key pointers are out of order: {:?} is not before {:?}",
pair[0].key, pair[1].key
)));
}
}
let mut block = vec![0u8; nodesize];
write_header(&mut block, sb, id, ptrs.len() as u32);
for (i, ptr) in ptrs.iter().enumerate() {
let at = LEAF_DATA_OFFSET + i * KEY_PTR_SIZE;
write_key(&mut block[at..], &ptr.key);
let k = at + DISK_KEY_SIZE;
block[k..k + 8].copy_from_slice(&ptr.blockptr.to_le_bytes());
block[k + 8..k + 16].copy_from_slice(&ptr.generation.to_le_bytes());
}
stamp_checksum(&mut block, sb);
Ok(block)
}
fn write_header(block: &mut [u8], sb: &Superblock, id: BlockIdentity, nritems: u32) {
block[o::FSID..o::FSID + 16].copy_from_slice(&sb.node_uuid());
block[o::BYTENR..o::BYTENR + 8].copy_from_slice(&id.bytenr.to_le_bytes());
block[o::FLAGS..o::FLAGS + 8].copy_from_slice(&id.flags.to_le_bytes());
block[o::CHUNK_TREE_UUID..o::CHUNK_TREE_UUID + 16].copy_from_slice(&id.chunk_tree_uuid);
block[o::GENERATION..o::GENERATION + 8].copy_from_slice(&id.generation.to_le_bytes());
block[o::OWNER..o::OWNER + 8].copy_from_slice(&id.owner.to_le_bytes());
block[o::NRITEMS..o::NRITEMS + 4].copy_from_slice(&nritems.to_le_bytes());
block[o::LEVEL] = id.level;
}
fn write_key(out: &mut [u8], key: &DiskKey) {
out[0..8].copy_from_slice(&key.objectid.to_le_bytes());
out[8] = key.key_type;
out[9..17].copy_from_slice(&key.offset.to_le_bytes());
}
pub fn stamp_checksum(block: &mut [u8], sb: &Superblock) {
let digest = sb.csum_type.digest(&block[CSUM_SIZE..]);
block[..CSUM_SIZE].copy_from_slice(&digest);
}
fn key_before(a: &DiskKey, b: &DiskKey) -> bool {
(a.objectid, a.key_type, a.offset) < (b.objectid, b.key_type, b.offset)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::btree::{KeyPtr, TreeBlock, TreeGeometry};
use crate::superblock::tests::{put64, reseal, sb_bytes, TEST_FSID};
use crate::superblock::{dev_item_offsets, incompat, offsets, UUID_SIZE};
fn sb_with_metadata_uuid() -> Superblock {
let mut b = sb_bytes();
let existing = u64::from_le_bytes(
b[offsets::INCOMPAT_FLAGS..offsets::INCOMPAT_FLAGS + 8]
.try_into()
.unwrap(),
);
put64(
&mut b,
offsets::INCOMPAT_FLAGS,
existing | incompat::METADATA_UUID,
);
let meta = [0x5Au8; UUID_SIZE];
b[offsets::METADATA_UUID..offsets::METADATA_UUID + UUID_SIZE].copy_from_slice(&meta);
let d = offsets::DEV_ITEM + dev_item_offsets::FSID;
b[d..d + UUID_SIZE].copy_from_slice(&meta);
reseal(&mut b);
let sb = Superblock::parse(&b).unwrap();
assert_eq!(sb.fsid, TEST_FSID);
assert_ne!(
sb.fsid,
sb.node_uuid(),
"the fixture must separate the two UUIDs, or it cannot tell them apart"
);
sb
}
fn identity(bytenr: u64, generation: u64, level: u8) -> BlockIdentity {
BlockIdentity {
bytenr,
owner: 5,
generation,
level,
flags: 1,
chunk_tree_uuid: [0x33; 16],
}
}
#[test]
fn a_block_we_write_carries_the_uuid_the_reader_demands() {
let sb = sb_with_metadata_uuid();
const AT: u64 = 0x2000_0000;
let block = build_leaf(&sb, identity(AT, 7, 0), &[]).expect("build a leaf");
TreeBlock::parse(block, AT, &TreeGeometry::from_superblock(&sb))
.expect("a block we wrote must be one we can read");
let child = KeyPtr {
key: key(1, 1, 0),
blockptr: 0x3000_0000,
generation: 7,
};
let node = build_node(&sb, identity(AT, 7, 1), &[child]).expect("build a node");
TreeBlock::parse(node, AT, &TreeGeometry::from_superblock(&sb))
.expect("and so must a node");
}
#[test]
fn a_block_we_write_on_an_ordinary_volume_still_reads_back() {
let sb = Superblock::parse(&sb_bytes()).unwrap();
assert_eq!(sb.fsid, sb.node_uuid(), "no METADATA_UUID on this one");
const AT: u64 = 0x2000_0000;
let block = build_leaf(&sb, identity(AT, 7, 0), &[]).expect("build a leaf");
let parsed = TreeBlock::parse(block, AT, &TreeGeometry::from_superblock(&sb))
.expect("a block we wrote must be one we can read");
assert_eq!(parsed.header.fsid, sb.fsid);
}
fn key(objectid: u64, key_type: u8, offset: u64) -> DiskKey {
DiskKey {
objectid,
key_type,
offset,
}
}
#[test]
fn keys_order_by_all_three_fields() {
assert!(key_before(&key(1, 0, 0), &key(2, 0, 0)), "objectid first");
assert!(key_before(&key(1, 1, 0), &key(1, 2, 0)), "then type");
assert!(key_before(&key(1, 1, 1), &key(1, 1, 2)), "then offset");
assert!(key_before(&key(1, 99, 0), &key(2, 0, 0)));
assert!(!key_before(&key(1, 0, 0), &key(1, 0, 0)), "strictly before");
}
#[test]
fn space_is_the_header_plus_each_item_and_its_body() {
let a = [1u8; 10];
let b = [2u8; 20];
let items = [
LeafItem {
key: key(1, 1, 0),
data: &a,
},
LeafItem {
key: key(2, 1, 0),
data: &b,
},
];
assert_eq!(space_needed(&items), HEADER_SIZE + 2 * ITEM_SIZE + 10 + 20);
assert_eq!(
space_needed(&[]),
HEADER_SIZE,
"an empty leaf is its header"
);
}
}