use crate::chunk::{key_type, DiskKey};
use crate::error::{Error, Result};
use crate::superblock::Superblock;
pub use crate::superblock::incompat::SKINNY_METADATA as INCOMPAT_SKINNY_METADATA;
pub const EXTENT_FLAG_DATA: u64 = 1 << 0;
pub const EXTENT_FLAG_TREE_BLOCK: u64 = 1 << 1;
pub const TREE_BLOCK_REF: u8 = 176;
pub const SKINNY_METADATA_ITEM_SIZE: usize = 33;
pub mod offsets {
pub const REFS: usize = 0;
pub const GENERATION: usize = 8;
pub const FLAGS: usize = 16;
pub const REF_TYPE: usize = 24;
pub const REF_OFFSET: usize = 25;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TreeBlockAllocation {
pub bytenr: u64,
pub level: u8,
pub generation: u64,
pub owner: u64,
}
impl TreeBlockAllocation {
pub fn key(&self) -> DiskKey {
DiskKey {
objectid: self.bytenr,
key_type: key_type::METADATA_ITEM,
offset: self.level as u64,
}
}
pub fn body(&self) -> [u8; SKINNY_METADATA_ITEM_SIZE] {
let mut out = [0u8; SKINNY_METADATA_ITEM_SIZE];
out[offsets::REFS..offsets::REFS + 8].copy_from_slice(&1u64.to_le_bytes());
out[offsets::GENERATION..offsets::GENERATION + 8]
.copy_from_slice(&self.generation.to_le_bytes());
out[offsets::FLAGS..offsets::FLAGS + 8]
.copy_from_slice(&EXTENT_FLAG_TREE_BLOCK.to_le_bytes());
out[offsets::REF_TYPE] = TREE_BLOCK_REF;
out[offsets::REF_OFFSET..offsets::REF_OFFSET + 8]
.copy_from_slice(&self.owner.to_le_bytes());
out
}
}
pub fn record_tree_block(
sb: &Superblock,
alloc: TreeBlockAllocation,
) -> Result<(DiskKey, [u8; SKINNY_METADATA_ITEM_SIZE])> {
if sb.incompat_flags & INCOMPAT_SKINNY_METADATA == 0 {
return Err(Error::UnsupportedFeature(
"this filesystem records tree blocks as EXTENT_ITEMs with a \
btrfs_tree_block_info, which is a longer item than this writes; recording an \
allocation without SKINNY_METADATA is not implemented"
.to_string(),
));
}
Ok((alloc.key(), alloc.body()))
}
pub fn used_after_allocating(group: &crate::block_group::BlockGroup, bytes: u64) -> Result<u64> {
if bytes > group.free() {
return Err(Error::UnsupportedFeature(format!(
"allocating {bytes} bytes in the block group at {} would take it past its own \
length: {} of {} bytes are already used",
group.start, group.used, group.length
)));
}
Ok(group.used + bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block_group::BlockGroup;
use crate::chunk::block_group as bg_flags;
#[test]
fn the_level_is_filed_in_the_key_not_the_body() {
let alloc = TreeBlockAllocation {
bytenr: 30_425_088,
level: 2,
generation: 7,
owner: 5,
};
let key = alloc.key();
assert_eq!(key.objectid, 30_425_088);
assert_eq!(key.key_type, key_type::METADATA_ITEM);
assert_eq!(key.offset, 2, "the offset is the level");
let other = TreeBlockAllocation { level: 0, ..alloc };
assert_eq!(alloc.body(), other.body());
}
#[test]
fn the_body_is_the_thirty_three_bytes_the_kernel_writes() {
let body = TreeBlockAllocation {
bytenr: 22_024_192,
level: 0,
generation: 6,
owner: 3,
}
.body();
assert_eq!(body.len(), 33);
assert_eq!(&body[0..8], &1u64.to_le_bytes(), "refs");
assert_eq!(&body[8..16], &6u64.to_le_bytes(), "generation");
assert_eq!(&body[16..24], &2u64.to_le_bytes(), "flags = TREE_BLOCK");
assert_eq!(body[24], 0xb0, "an inline TREE_BLOCK_REF");
assert_eq!(&body[25..33], &3u64.to_le_bytes(), "the owning tree");
}
#[test]
fn a_tree_block_is_not_flagged_as_data() {
let body = TreeBlockAllocation {
bytenr: 4096,
level: 1,
generation: 1,
owner: 5,
}
.body();
let flags = u64::from_le_bytes(body[16..24].try_into().unwrap());
assert_eq!(flags & EXTENT_FLAG_TREE_BLOCK, EXTENT_FLAG_TREE_BLOCK);
assert_eq!(flags & EXTENT_FLAG_DATA, 0);
}
fn group(used: u64, length: u64) -> BlockGroup {
BlockGroup {
start: 22_020_096,
length,
used,
flags: bg_flags::METADATA,
}
}
#[test]
fn allocating_moves_the_groups_used_count_up() {
let g = group(16384, 1 << 20);
assert_eq!(used_after_allocating(&g, 4096).unwrap(), 20480);
let g = group((1 << 20) - 4096, 1 << 20);
assert_eq!(used_after_allocating(&g, 4096).unwrap(), 1 << 20);
}
#[test]
fn overfilling_a_group_is_refused() {
let g = group(1 << 20, 1 << 20);
assert!(used_after_allocating(&g, 4096).is_err(), "a full group");
let g = group((1 << 20) - 1, 1 << 20);
assert!(
used_after_allocating(&g, 4096).is_err(),
"one free byte is not four thousand"
);
}
}