#![expect(
dead_code,
reason = "format-reference completeness: parsed fields are materialised for the entire \
on-disk struct so that the parser stays one-to-one with the layout, even when \
the v0.1.x read paths only consume a subset of those fields"
)]
use crate::{
format::constants::{CSUM_LEN, FSID_LEN, UUID_LEN},
util::{read_le_u16, read_le_u32, read_le_u64, read_le_u8},
};
mod sb {
use super::UUID_LEN;
pub(super) const OFFSET_CSUM: usize = 0;
pub(super) const OFFSET_FSID: usize = 32; pub(super) const OFFSET_BYTENR: usize = 48;
pub(super) const OFFSET_FLAGS: usize = 56;
pub(super) const OFFSET_MAGIC: usize = 64;
pub(super) const OFFSET_GENERATION: usize = 72;
pub(super) const OFFSET_ROOT: usize = 80;
pub(super) const OFFSET_CHUNK_ROOT: usize = 88;
pub(super) const OFFSET_LOG_ROOT: usize = 96;
pub(super) const OFFSET_LOG_ROOT_TRANSID: usize = 104;
pub(super) const OFFSET_TOTAL_BYTES: usize = 112;
pub(super) const OFFSET_BYTES_USED: usize = 120;
pub(super) const OFFSET_ROOT_DIR_OBJECTID: usize = 128;
pub(super) const OFFSET_NUM_DEVICES: usize = 136;
pub(super) const OFFSET_SECTORSIZE: usize = 144;
pub(super) const OFFSET_NODESIZE: usize = 148;
pub(super) const OFFSET_LEAFSIZE: usize = 152;
pub(super) const OFFSET_STRIPESIZE: usize = 156;
pub(super) const OFFSET_SYS_CHUNK_ARRAY_SIZE: usize = 160;
pub(super) const OFFSET_CHUNK_ROOT_GENERATION: usize = 164;
pub(super) const OFFSET_COMPAT_FLAGS: usize = 172;
pub(super) const OFFSET_COMPAT_RO_FLAGS: usize = 180;
pub(super) const OFFSET_INCOMPAT_FLAGS: usize = 188;
pub(super) const OFFSET_CSUM_TYPE: usize = 196;
pub(super) const OFFSET_ROOT_LEVEL: usize = 198;
pub(super) const OFFSET_CHUNK_ROOT_LEVEL: usize = 199;
pub(super) const OFFSET_LOG_ROOT_LEVEL: usize = 200;
pub(super) const OFFSET_DEV_ITEM: usize = 201;
pub(super) const SIZE_DEV_ITEM: usize = 98;
pub(super) const OFFSET_LABEL: usize = OFFSET_DEV_ITEM + SIZE_DEV_ITEM; pub(super) const SIZE_LABEL: usize = 256;
pub(super) const OFFSET_CACHE_GENERATION: usize = OFFSET_LABEL + SIZE_LABEL; pub(super) const OFFSET_UUID_TREE_GENERATION: usize = OFFSET_CACHE_GENERATION + 8; pub(super) const OFFSET_METADATA_UUID: usize = OFFSET_UUID_TREE_GENERATION + 8; pub(super) const OFFSET_NR_GLOBAL_ROOTS: usize = OFFSET_METADATA_UUID + UUID_LEN; pub(super) const OFFSET_SYS_CHUNK_ARRAY: usize = 811; pub(super) const SIZE_SYS_CHUNK_ARRAY: usize = 2048;
}
#[derive(Debug, Clone)]
pub(crate) struct Superblock {
pub(crate) csum: [u8; CSUM_LEN],
pub(crate) fsid: [u8; FSID_LEN],
pub(crate) bytenr: u64,
pub(crate) flags: u64,
pub(crate) magic: u64,
pub(crate) generation: u64,
pub(crate) root: u64,
pub(crate) chunk_root: u64,
pub(crate) log_root: u64,
pub(crate) total_bytes: u64,
pub(crate) bytes_used: u64,
pub(crate) root_dir_objectid: u64,
pub(crate) num_devices: u64,
pub(crate) sectorsize: u32,
pub(crate) nodesize: u32,
pub(crate) leafsize: u32,
pub(crate) stripesize: u32,
pub(crate) sys_chunk_array_size: u32,
pub(crate) chunk_root_generation: u64,
pub(crate) compat_flags: u64,
pub(crate) compat_ro_flags: u64,
pub(crate) incompat_flags: u64,
pub(crate) csum_type: u16,
pub(crate) root_level: u8,
pub(crate) chunk_root_level: u8,
pub(crate) log_root_level: u8,
pub(crate) sys_chunk_array: [u8; sb::SIZE_SYS_CHUNK_ARRAY],
}
impl Superblock {
pub(crate) fn parse(buf: &[u8]) -> Option<Self> {
if buf.len() < crate::format::constants::SUPERBLOCK_SIZE {
return None;
}
let mut csum = [0u8; CSUM_LEN];
csum.copy_from_slice(&buf[sb::OFFSET_CSUM..sb::OFFSET_CSUM + CSUM_LEN]);
let mut fsid = [0u8; FSID_LEN];
fsid.copy_from_slice(&buf[sb::OFFSET_FSID..sb::OFFSET_FSID + FSID_LEN]);
let mut sys_chunk_array = [0u8; sb::SIZE_SYS_CHUNK_ARRAY];
sys_chunk_array.copy_from_slice(
&buf[sb::OFFSET_SYS_CHUNK_ARRAY..sb::OFFSET_SYS_CHUNK_ARRAY + sb::SIZE_SYS_CHUNK_ARRAY],
);
Some(Self {
csum,
fsid,
bytenr: read_le_u64(buf, sb::OFFSET_BYTENR),
flags: read_le_u64(buf, sb::OFFSET_FLAGS),
magic: read_le_u64(buf, sb::OFFSET_MAGIC),
generation: read_le_u64(buf, sb::OFFSET_GENERATION),
root: read_le_u64(buf, sb::OFFSET_ROOT),
chunk_root: read_le_u64(buf, sb::OFFSET_CHUNK_ROOT),
log_root: read_le_u64(buf, sb::OFFSET_LOG_ROOT),
total_bytes: read_le_u64(buf, sb::OFFSET_TOTAL_BYTES),
bytes_used: read_le_u64(buf, sb::OFFSET_BYTES_USED),
root_dir_objectid: read_le_u64(buf, sb::OFFSET_ROOT_DIR_OBJECTID),
num_devices: read_le_u64(buf, sb::OFFSET_NUM_DEVICES),
sectorsize: read_le_u32(buf, sb::OFFSET_SECTORSIZE),
nodesize: read_le_u32(buf, sb::OFFSET_NODESIZE),
leafsize: read_le_u32(buf, sb::OFFSET_LEAFSIZE),
stripesize: read_le_u32(buf, sb::OFFSET_STRIPESIZE),
sys_chunk_array_size: read_le_u32(buf, sb::OFFSET_SYS_CHUNK_ARRAY_SIZE),
chunk_root_generation: read_le_u64(buf, sb::OFFSET_CHUNK_ROOT_GENERATION),
compat_flags: read_le_u64(buf, sb::OFFSET_COMPAT_FLAGS),
compat_ro_flags: read_le_u64(buf, sb::OFFSET_COMPAT_RO_FLAGS),
incompat_flags: read_le_u64(buf, sb::OFFSET_INCOMPAT_FLAGS),
csum_type: read_le_u16(buf, sb::OFFSET_CSUM_TYPE),
root_level: read_le_u8(buf, sb::OFFSET_ROOT_LEVEL),
chunk_root_level: read_le_u8(buf, sb::OFFSET_CHUNK_ROOT_LEVEL),
log_root_level: read_le_u8(buf, sb::OFFSET_LOG_ROOT_LEVEL),
sys_chunk_array,
})
}
pub(crate) const fn body_offset_for_csum() -> usize {
CSUM_LEN
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct DiskKey {
pub(crate) objectid: u64,
pub(crate) item_type: u8,
pub(crate) offset: u64,
}
impl DiskKey {
pub(crate) const SIZE: usize = 17;
pub(crate) fn parse(buf: &[u8], at: usize) -> Self {
Self {
objectid: read_le_u64(buf, at),
item_type: read_le_u8(buf, at + 8),
offset: read_le_u64(buf, at + 9),
}
}
pub(crate) fn cmp_tuple(&self) -> (u64, u8, u64) {
(self.objectid, self.item_type, self.offset)
}
}
impl PartialOrd for DiskKey {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DiskKey {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.cmp_tuple().cmp(&other.cmp_tuple())
}
}
#[derive(Debug, Clone)]
pub(crate) struct Header {
pub(crate) csum: [u8; CSUM_LEN],
pub(crate) fsid: [u8; FSID_LEN],
pub(crate) bytenr: u64,
pub(crate) flags: u64,
pub(crate) chunk_tree_uuid: [u8; UUID_LEN],
pub(crate) generation: u64,
pub(crate) owner: u64,
pub(crate) nritems: u32,
pub(crate) level: u8,
}
mod hdr {
use super::{CSUM_LEN, FSID_LEN, UUID_LEN};
pub(super) const OFFSET_CSUM: usize = 0;
pub(super) const OFFSET_FSID: usize = OFFSET_CSUM + CSUM_LEN; pub(super) const OFFSET_BYTENR: usize = OFFSET_FSID + FSID_LEN; pub(super) const OFFSET_FLAGS: usize = OFFSET_BYTENR + 8; pub(super) const OFFSET_CHUNK_UUID: usize = OFFSET_FLAGS + 8; pub(super) const OFFSET_GENERATION: usize = OFFSET_CHUNK_UUID + UUID_LEN; pub(super) const OFFSET_OWNER: usize = OFFSET_GENERATION + 8; pub(super) const OFFSET_NRITEMS: usize = OFFSET_OWNER + 8; pub(super) const OFFSET_LEVEL: usize = OFFSET_NRITEMS + 4; pub(super) const SIZE: usize = OFFSET_LEVEL + 1; pub(super) const HEADER_END: usize = 101;
}
impl Header {
pub(crate) const SIZE: usize = hdr::HEADER_END;
pub(crate) fn parse(buf: &[u8]) -> Option<Self> {
if buf.len() < Self::SIZE {
return None;
}
let mut csum = [0u8; CSUM_LEN];
csum.copy_from_slice(&buf[hdr::OFFSET_CSUM..hdr::OFFSET_CSUM + CSUM_LEN]);
let mut fsid = [0u8; FSID_LEN];
fsid.copy_from_slice(&buf[hdr::OFFSET_FSID..hdr::OFFSET_FSID + FSID_LEN]);
let mut chunk_tree_uuid = [0u8; UUID_LEN];
chunk_tree_uuid
.copy_from_slice(&buf[hdr::OFFSET_CHUNK_UUID..hdr::OFFSET_CHUNK_UUID + UUID_LEN]);
Some(Self {
csum,
fsid,
bytenr: read_le_u64(buf, hdr::OFFSET_BYTENR),
flags: read_le_u64(buf, hdr::OFFSET_FLAGS),
chunk_tree_uuid,
generation: read_le_u64(buf, hdr::OFFSET_GENERATION),
owner: read_le_u64(buf, hdr::OFFSET_OWNER),
nritems: read_le_u32(buf, hdr::OFFSET_NRITEMS),
level: read_le_u8(buf, hdr::OFFSET_LEVEL),
})
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct LeafItem {
pub(crate) key: DiskKey,
pub(crate) offset: u32,
pub(crate) size: u32,
}
impl LeafItem {
pub(crate) const SIZE: usize = DiskKey::SIZE + 4 + 4;
pub(crate) fn parse(buf: &[u8], at: usize) -> Self {
let key = DiskKey::parse(buf, at);
let offset = read_le_u32(buf, at + DiskKey::SIZE);
let size = read_le_u32(buf, at + DiskKey::SIZE + 4);
Self { key, offset, size }
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct KeyPtr {
pub(crate) key: DiskKey,
pub(crate) blockptr: u64,
pub(crate) generation: u64,
}
impl KeyPtr {
pub(crate) const SIZE: usize = DiskKey::SIZE + 8 + 8;
pub(crate) fn parse(buf: &[u8], at: usize) -> Self {
let key = DiskKey::parse(buf, at);
let blockptr = read_le_u64(buf, at + DiskKey::SIZE);
let generation = read_le_u64(buf, at + DiskKey::SIZE + 8);
Self {
key,
blockptr,
generation,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct ChunkItem {
pub(crate) length: u64,
pub(crate) owner: u64,
pub(crate) stripe_len: u64,
pub(crate) ty: u64, pub(crate) io_align: u32,
pub(crate) io_width: u32,
pub(crate) sector_size: u32,
pub(crate) num_stripes: u16,
pub(crate) sub_stripes: u16,
pub(crate) stripes_offset: usize,
}
impl ChunkItem {
pub(crate) const HEADER_SIZE: usize = 48;
pub(crate) fn parse(buf: &[u8], at: usize) -> Option<Self> {
if buf.len() < at + Self::HEADER_SIZE {
return None;
}
Some(Self {
length: read_le_u64(buf, at),
owner: read_le_u64(buf, at + 8),
stripe_len: read_le_u64(buf, at + 16),
ty: read_le_u64(buf, at + 24),
io_align: read_le_u32(buf, at + 32),
io_width: read_le_u32(buf, at + 36),
sector_size: read_le_u32(buf, at + 40),
num_stripes: read_le_u16(buf, at + 44),
sub_stripes: read_le_u16(buf, at + 46),
stripes_offset: at + Self::HEADER_SIZE,
})
}
pub(crate) fn total_size(num_stripes: u16) -> usize {
Self::HEADER_SIZE + Stripe::SIZE * num_stripes as usize
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Stripe {
pub(crate) devid: u64,
pub(crate) offset: u64,
pub(crate) dev_uuid: [u8; UUID_LEN],
}
impl Stripe {
pub(crate) const SIZE: usize = 8 + 8 + UUID_LEN;
pub(crate) fn parse(buf: &[u8], at: usize) -> Self {
let devid = read_le_u64(buf, at);
let offset = read_le_u64(buf, at + 8);
let mut dev_uuid = [0u8; UUID_LEN];
dev_uuid.copy_from_slice(&buf[at + 16..at + 16 + UUID_LEN]);
Self {
devid,
offset,
dev_uuid,
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct InodeItem {
pub(crate) generation: u64,
pub(crate) transid: u64,
pub(crate) size: u64,
pub(crate) nbytes: u64,
pub(crate) block_group: u64,
pub(crate) nlink: u32,
pub(crate) uid: u32,
pub(crate) gid: u32,
pub(crate) mode: u32,
pub(crate) rdev: u64,
pub(crate) flags: u64,
pub(crate) sequence: u64,
}
impl InodeItem {
pub(crate) const SIZE: usize = 160;
pub(crate) fn parse(buf: &[u8], at: usize) -> Option<Self> {
if buf.len() < at + Self::SIZE {
return None;
}
Some(Self {
generation: read_le_u64(buf, at),
transid: read_le_u64(buf, at + 8),
size: read_le_u64(buf, at + 16),
nbytes: read_le_u64(buf, at + 24),
block_group: read_le_u64(buf, at + 32),
nlink: read_le_u32(buf, at + 40),
uid: read_le_u32(buf, at + 44),
gid: read_le_u32(buf, at + 48),
mode: read_le_u32(buf, at + 52),
rdev: read_le_u64(buf, at + 56),
flags: read_le_u64(buf, at + 64),
sequence: read_le_u64(buf, at + 72),
})
}
}
#[derive(Debug, Clone)]
pub(crate) struct RootItem {
pub(crate) inode: InodeItem,
pub(crate) generation: u64,
pub(crate) root_dirid: u64,
pub(crate) bytenr: u64,
pub(crate) byte_limit: u64,
pub(crate) bytes_used: u64,
pub(crate) last_snapshot: u64,
pub(crate) flags: u64,
pub(crate) refs: u32,
pub(crate) drop_progress: DiskKey,
pub(crate) drop_level: u8,
pub(crate) level: u8,
}
impl RootItem {
pub(crate) const MIN_SIZE: usize =
InodeItem::SIZE + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + DiskKey::SIZE + 1 + 1;
pub(crate) fn parse(buf: &[u8], at: usize) -> Option<Self> {
if buf.len() < at + Self::MIN_SIZE {
return None;
}
let inode = InodeItem::parse(buf, at)?;
let mut p = at + InodeItem::SIZE;
let generation = read_le_u64(buf, p);
p += 8;
let root_dirid = read_le_u64(buf, p);
p += 8;
let bytenr = read_le_u64(buf, p);
p += 8;
let byte_limit = read_le_u64(buf, p);
p += 8;
let bytes_used = read_le_u64(buf, p);
p += 8;
let last_snapshot = read_le_u64(buf, p);
p += 8;
let flags = read_le_u64(buf, p);
p += 8;
let refs = read_le_u32(buf, p);
p += 4;
let drop_progress = DiskKey::parse(buf, p);
p += DiskKey::SIZE;
let drop_level = read_le_u8(buf, p);
p += 1;
let level = read_le_u8(buf, p);
Some(Self {
inode,
generation,
root_dirid,
bytenr,
byte_limit,
bytes_used,
last_snapshot,
flags,
refs,
drop_progress,
drop_level,
level,
})
}
}
#[derive(Debug, Clone)]
pub(crate) struct DirEntry<'a> {
pub(crate) location: DiskKey,
pub(crate) transid: u64,
pub(crate) data_len: u16,
pub(crate) name_len: u16,
pub(crate) ty: u8,
pub(crate) name: &'a [u8],
}
impl<'a> DirEntry<'a> {
pub(crate) const HEADER_SIZE: usize = DiskKey::SIZE + 8 + 2 + 2 + 1;
pub(crate) fn parse(buf: &'a [u8], at: usize) -> Option<(Self, usize)> {
if buf.len() < at + Self::HEADER_SIZE {
return None;
}
let location = DiskKey::parse(buf, at);
let transid = read_le_u64(buf, at + DiskKey::SIZE);
let data_len = read_le_u16(buf, at + DiskKey::SIZE + 8);
let name_len = read_le_u16(buf, at + DiskKey::SIZE + 10);
let ty = read_le_u8(buf, at + DiskKey::SIZE + 12);
let name_start = at + Self::HEADER_SIZE;
let name_end = name_start + name_len as usize;
let data_end = name_end + data_len as usize;
if buf.len() < data_end {
return None;
}
Some((
Self {
location,
transid,
data_len,
name_len,
ty,
name: &buf[name_start..name_end],
},
data_end,
))
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ExtentDataHeader {
pub(crate) generation: u64,
pub(crate) ram_bytes: u64,
pub(crate) compression: u8,
pub(crate) encryption: u8,
pub(crate) other_encoding: u16,
pub(crate) ty: u8,
}
impl ExtentDataHeader {
pub(crate) const SIZE: usize = 21;
pub(crate) fn parse(buf: &[u8], at: usize) -> Option<Self> {
if buf.len() < at + Self::SIZE {
return None;
}
Some(Self {
generation: read_le_u64(buf, at),
ram_bytes: read_le_u64(buf, at + 8),
compression: read_le_u8(buf, at + 16),
encryption: read_le_u8(buf, at + 17),
other_encoding: read_le_u16(buf, at + 18),
ty: read_le_u8(buf, at + 20),
})
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ExtentDataRegular {
pub(crate) disk_bytenr: u64,
pub(crate) disk_num_bytes: u64,
pub(crate) offset: u64,
pub(crate) num_bytes: u64,
}
impl ExtentDataRegular {
pub(crate) const SIZE: usize = 32;
pub(crate) fn parse(buf: &[u8], at: usize) -> Option<Self> {
if buf.len() < at + Self::SIZE {
return None;
}
Some(Self {
disk_bytenr: read_le_u64(buf, at),
disk_num_bytes: read_le_u64(buf, at + 8),
offset: read_le_u64(buf, at + 16),
num_bytes: read_le_u64(buf, at + 24),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn disk_key_ordering() {
let a = DiskKey {
objectid: 1,
item_type: 1,
offset: 0,
};
let b = DiskKey {
objectid: 1,
item_type: 1,
offset: 1,
};
let c = DiskKey {
objectid: 1,
item_type: 2,
offset: 0,
};
let d = DiskKey {
objectid: 2,
item_type: 0,
offset: 0,
};
assert!(a < b && b < c && c < d);
}
#[test]
fn disk_key_parse_round_trips_offsets() {
let mut buf = [0u8; DiskKey::SIZE];
buf[0..8].copy_from_slice(&42u64.to_le_bytes());
buf[8] = 84;
buf[9..17].copy_from_slice(&7u64.to_le_bytes());
let key = DiskKey::parse(&buf, 0);
assert_eq!(key.objectid, 42);
assert_eq!(key.item_type, 84);
assert_eq!(key.offset, 7);
}
}