use crate::bytes::{le_u16, le_u32, le_u64, u8_at};
use crate::chunk::{SysChunk, SYS_CHUNK_ARRAY_OFFSET};
use crate::crc::{superblock_crc_status, CsumType, BTRFS_CSUM_SIZE};
use crate::error::BtrfsError;
pub const BTRFS_SUPER_INFO_OFFSET: u64 = 65536;
pub const BTRFS_SUPER_INFO_SIZE: usize = 4096;
pub const BTRFS_MAGIC: [u8; 8] = *b"_BHRfS_M";
const MAGIC_OFFSET: usize = 0x40;
pub const BTRFS_FSID_SIZE: usize = 16;
pub const BTRFS_LABEL_SIZE: usize = 256;
const SB_MIN_LEN: usize = 0xc9;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Superblock {
pub magic: [u8; 8],
pub csum: [u8; 4],
pub fsid: [u8; BTRFS_FSID_SIZE],
pub bytenr: u64,
pub flags: u64,
pub generation: u64,
pub root: u64,
pub chunk_root: u64,
pub log_root: u64,
pub total_bytes: u64,
pub bytes_used: u64,
pub root_dir_objectid: u64,
pub num_devices: u64,
pub sectorsize: u32,
pub nodesize: u32,
pub stripesize: u32,
pub sys_chunk_array_size: u32,
pub chunk_root_generation: u64,
pub compat_flags: u64,
pub compat_ro_flags: u64,
pub incompat_flags: u64,
pub csum_type: CsumType,
pub root_level: u8,
pub chunk_root_level: u8,
pub log_root_level: u8,
pub label: String,
pub sys_chunks: Vec<SysChunk>,
pub crc_valid: Option<bool>,
}
impl Superblock {
pub fn parse(block: &[u8]) -> Result<Self, BtrfsError> {
let mut magic = [0u8; 8];
for (i, b) in magic.iter_mut().enumerate() {
*b = u8_at(block, MAGIC_OFFSET + i);
}
if magic != BTRFS_MAGIC {
return Err(BtrfsError::BadMagic { bytes: magic });
}
if block.len() < SB_MIN_LEN {
return Err(BtrfsError::Truncated {
structure: "superblock",
need: SB_MIN_LEN,
have: block.len(),
});
}
let mut csum = [0u8; 4];
for (i, b) in csum.iter_mut().enumerate() {
*b = u8_at(block, i);
}
let mut fsid = [0u8; BTRFS_FSID_SIZE];
for (i, b) in fsid.iter_mut().enumerate() {
*b = u8_at(block, 0x20 + i);
}
let sectorsize = le_u32(block, 0x90);
let csum_type = CsumType::from_code(le_u16(block, 0xc4));
let covered = (sectorsize as usize).min(block.len()).max(BTRFS_CSUM_SIZE);
let crc_valid = superblock_crc_status(csum_type, block, covered);
let sys_chunk_array_size = le_u32(block, 0xa0);
let sys_chunks = SysChunk::parse_array(block, SYS_CHUNK_ARRAY_OFFSET, sys_chunk_array_size);
let label = decode_label(block, 0x12b);
Ok(Self {
magic,
csum,
fsid,
bytenr: le_u64(block, 0x30),
flags: le_u64(block, 0x38),
generation: le_u64(block, 0x48),
root: le_u64(block, 0x50),
chunk_root: le_u64(block, 0x58),
log_root: le_u64(block, 0x60),
total_bytes: le_u64(block, 0x70),
bytes_used: le_u64(block, 0x78),
root_dir_objectid: le_u64(block, 0x80),
num_devices: le_u64(block, 0x88),
sectorsize,
nodesize: le_u32(block, 0x94),
stripesize: le_u32(block, 0x9c),
sys_chunk_array_size,
chunk_root_generation: le_u64(block, 0xa4),
compat_flags: le_u64(block, 0xac),
compat_ro_flags: le_u64(block, 0xb4),
incompat_flags: le_u64(block, 0xbc),
csum_type,
root_level: u8_at(block, 0xc6),
chunk_root_level: u8_at(block, 0xc7),
log_root_level: u8_at(block, 0xc8),
label,
sys_chunks,
crc_valid,
})
}
#[must_use]
pub fn fsid_string(&self) -> String {
uuid_string(&self.fsid)
}
}
fn decode_label(block: &[u8], off: usize) -> String {
let end = off.saturating_add(BTRFS_LABEL_SIZE).min(block.len());
let raw = block.get(off..end).unwrap_or(&[]);
let trimmed = raw.split(|&b| b == 0).next().unwrap_or(&[]);
String::from_utf8_lossy(trimmed).into_owned()
}
fn uuid_string(u: &[u8; 16]) -> String {
let h: Vec<String> = u.iter().map(|b| format!("{b:02x}")).collect();
format!(
"{}{}{}{}-{}{}-{}{}-{}{}-{}{}{}{}{}{}",
h[0],
h[1],
h[2],
h[3],
h[4],
h[5],
h[6],
h[7],
h[8],
h[9],
h[10],
h[11],
h[12],
h[13],
h[14],
h[15]
)
}