use crate::bytes::{le_u16, le_u32, le_u64, u8_at};
use crate::chunk::{Stripe, DISK_KEY_SIZE, STRIPE_SIZE};
use crate::crc::{superblock_crc_status, CsumType};
use crate::error::BtrfsError;
use crate::superblock::Superblock;
use crate::DiskKey;
pub const BTRFS_HEADER_SIZE: usize = 101;
pub const BTRFS_ITEM_SIZE: usize = DISK_KEY_SIZE + 4 + 4;
pub const BTRFS_KEY_PTR_SIZE: usize = DISK_KEY_SIZE + 8 + 8;
pub const CHUNK_HEADER_SIZE: usize = 48;
pub const CHUNK_TREE_OBJECTID: u64 = 3;
mod hdr {
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 const FSID: usize = 0x20;
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Header {
pub csum: [u8; 4],
pub fsid: [u8; 16],
pub bytenr: u64,
pub flags: u64,
pub chunk_tree_uuid: [u8; 16],
pub generation: u64,
pub owner: u64,
pub nritems: u32,
pub level: u8,
}
impl Header {
#[must_use]
fn parse(block: &[u8]) -> Option<Self> {
if block.len() < BTRFS_HEADER_SIZE {
return None;
}
let mut csum = [0u8; 4];
for (i, b) in csum.iter_mut().enumerate() {
*b = u8_at(block, i);
}
let mut fsid = [0u8; 16];
for (i, b) in fsid.iter_mut().enumerate() {
*b = u8_at(block, hdr::FSID + i);
}
let mut chunk_tree_uuid = [0u8; 16];
for (i, b) in chunk_tree_uuid.iter_mut().enumerate() {
*b = u8_at(block, hdr::CHUNK_TREE_UUID + i);
}
Some(Header {
csum,
fsid,
bytenr: le_u64(block, hdr::BYTENR),
flags: le_u64(block, hdr::FLAGS),
chunk_tree_uuid,
generation: le_u64(block, hdr::GENERATION),
owner: le_u64(block, hdr::OWNER),
nritems: le_u32(block, hdr::NRITEMS),
level: u8_at(block, hdr::LEVEL),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyPtr {
pub key: DiskKey,
pub blockptr: u64,
pub generation: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Chunk {
pub length: u64,
pub owner: u64,
pub stripe_len: u64,
pub chunk_type: u64,
pub num_stripes: u16,
pub sub_stripes: u16,
pub stripes: Vec<Stripe>,
}
impl Chunk {
#[must_use]
fn parse(data: &[u8]) -> Self {
let num_stripes = le_u16(data, 44);
let avail = data.len().saturating_sub(CHUNK_HEADER_SIZE);
let max_fit = avail / STRIPE_SIZE;
let take = usize::from(num_stripes).min(max_fit);
let mut stripes = Vec::with_capacity(take);
for i in 0..take {
let so = CHUNK_HEADER_SIZE + i * STRIPE_SIZE;
let mut dev_uuid = [0u8; 16];
for (j, b) in dev_uuid.iter_mut().enumerate() {
*b = u8_at(data, so + 16 + j);
}
stripes.push(Stripe {
devid: le_u64(data, so),
offset: le_u64(data, so + 8),
dev_uuid,
});
}
Chunk {
length: le_u64(data, 0),
owner: le_u64(data, 8),
stripe_len: le_u64(data, 16),
chunk_type: le_u64(data, 24),
num_stripes,
sub_stripes: le_u16(data, 46),
stripes,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Body {
Leaf(Vec<ItemHeader>),
Interior(Vec<KeyPtr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ItemHeader {
key: DiskKey,
data_offset: u32,
data_size: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Node {
pub header: Header,
pub crc_valid: Option<bool>,
body: Body,
block: Vec<u8>,
}
impl Node {
pub fn parse(block: &[u8]) -> Result<Self, BtrfsError> {
let Some(header) = Header::parse(block) else {
return Err(BtrfsError::Truncated {
structure: "btrfs_header",
need: BTRFS_HEADER_SIZE,
have: block.len(),
});
};
let crc_valid = superblock_crc_status(CsumType::Crc32c, block, block.len());
let nritems = header.nritems as usize;
let body = if header.level == 0 {
Body::Leaf(parse_items(block, nritems))
} else {
Body::Interior(parse_key_ptrs(block, nritems))
};
Ok(Node {
header,
crc_valid,
body,
block: block.to_vec(),
})
}
#[must_use]
pub fn is_leaf(&self) -> bool {
matches!(self.body, Body::Leaf(_))
}
pub fn leaf_items(&self) -> impl Iterator<Item = (DiskKey, &[u8])> {
let items: &[ItemHeader] = match &self.body {
Body::Leaf(items) => items,
Body::Interior(_) => &[],
};
items.iter().map(move |it| (it.key, self.item_data(it)))
}
fn item_data(&self, it: &ItemHeader) -> &[u8] {
let start = BTRFS_HEADER_SIZE.saturating_add(it.data_offset as usize);
let end = start.saturating_add(it.data_size as usize);
self.block.get(start..end).unwrap_or(&[])
}
#[must_use]
pub fn key_ptrs(&self) -> &[KeyPtr] {
match &self.body {
Body::Interior(ptrs) => ptrs,
Body::Leaf(_) => &[],
}
}
#[must_use]
pub fn chunk_items(&self) -> Vec<(u64, Chunk)> {
self.leaf_items()
.filter(|(key, _)| key.key_type == crate::CHUNK_ITEM_KEY)
.map(|(key, data)| (key.offset, Chunk::parse(data)))
.collect()
}
}
fn parse_items(block: &[u8], nritems: usize) -> Vec<ItemHeader> {
let avail = block.len().saturating_sub(BTRFS_HEADER_SIZE);
let max_fit = avail / BTRFS_ITEM_SIZE;
let take = nritems.min(max_fit);
let mut out = Vec::with_capacity(take);
for i in 0..take {
let io = BTRFS_HEADER_SIZE + i * BTRFS_ITEM_SIZE;
out.push(ItemHeader {
key: DiskKey::parse(block, io),
data_offset: le_u32(block, io + DISK_KEY_SIZE),
data_size: le_u32(block, io + DISK_KEY_SIZE + 4),
});
}
out
}
fn parse_key_ptrs(block: &[u8], nritems: usize) -> Vec<KeyPtr> {
let avail = block.len().saturating_sub(BTRFS_HEADER_SIZE);
let max_fit = avail / BTRFS_KEY_PTR_SIZE;
let take = nritems.min(max_fit);
let mut out = Vec::with_capacity(take);
for i in 0..take {
let po = BTRFS_HEADER_SIZE + i * BTRFS_KEY_PTR_SIZE;
out.push(KeyPtr {
key: DiskKey::parse(block, po),
blockptr: le_u64(block, po + DISK_KEY_SIZE),
generation: le_u64(block, po + DISK_KEY_SIZE + 8),
});
}
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct MapEntry {
logical_start: u64,
length: u64,
stripes: Vec<(u64, u64)>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ChunkMap {
entries: Vec<MapEntry>,
}
impl ChunkMap {
#[must_use]
pub fn new() -> Self {
ChunkMap {
entries: Vec::new(),
}
}
pub fn add_from_node(&mut self, node: &Node) {
for (logical, chunk) in node.chunk_items() {
self.entries.push(MapEntry {
logical_start: logical,
length: chunk.length,
stripes: chunk.stripes.iter().map(|s| (s.devid, s.offset)).collect(),
});
}
}
#[must_use]
pub fn logical_to_physical(&self, logical: u64) -> Option<(u64, u64)> {
for e in &self.entries {
let end = e.logical_start.checked_add(e.length)?;
if logical >= e.logical_start && logical < end {
let (devid, phys_start) = *e.stripes.first()?;
let delta = logical - e.logical_start;
let phys = phys_start.checked_add(delta)?;
return Some((devid, phys));
}
}
None
}
pub fn walk(image: &[u8], sb: &Superblock) -> Result<Self, BtrfsError> {
let mut boot = ChunkMap::new();
for c in &sb.sys_chunks {
boot.entries.push(MapEntry {
logical_start: c.key.offset,
length: c.length,
stripes: c.stripes.iter().map(|s| (s.devid, s.offset)).collect(),
});
}
let nodesize = sb.nodesize as usize;
let mut map = ChunkMap::new();
let mut budget: usize = 4096;
let mut stack = vec![sb.chunk_root];
let mut bootstrapped_root = false;
while let Some(logical) = stack.pop() {
if budget == 0 {
break; }
budget -= 1;
let phys = map
.logical_to_physical(logical)
.or_else(|| boot.logical_to_physical(logical));
let Some((_devid, phys)) = phys else {
if !bootstrapped_root {
return Err(BtrfsError::Truncated {
structure: "chunk_root logical (sys_chunk_array bootstrap)",
need: logical as usize,
have: 0,
});
} continue; };
bootstrapped_root = true;
let start = phys as usize;
let Some(block) = image.get(start..start.saturating_add(nodesize)) else {
if map.entries.is_empty() {
return Err(BtrfsError::Truncated {
structure: "chunk_root node (out of image)",
need: start.saturating_add(nodesize),
have: image.len(),
});
} continue; };
let Ok(node) = Node::parse(block) else {
continue; };
if node.is_leaf() {
map.add_from_node(&node);
} else {
for kp in node.key_ptrs() {
stack.push(kp.blockptr);
}
}
}
Ok(map)
}
}
pub fn read_node(
image: &[u8],
sb: &Superblock,
chunk_map: &ChunkMap,
logical: u64,
) -> Result<Node, BtrfsError> {
let phys = chunk_map.logical_to_physical(logical).or_else(|| {
sb.sys_chunks
.iter()
.find_map(|c| c.logical_to_physical(logical).map(|p| (0u64, p)))
});
let Some((_devid, phys)) = phys else {
return Err(BtrfsError::Truncated {
structure: "logical address (no chunk mapping)",
need: logical as usize,
have: 0,
});
};
let nodesize = sb.nodesize as usize;
let start = phys as usize;
let Some(block) = image.get(start..start.saturating_add(nodesize)) else {
return Err(BtrfsError::Truncated {
structure: "node block (out of image)",
need: start.saturating_add(nodesize),
have: image.len(),
});
};
Node::parse(block)
}