use crate::object::{fletcher64_checksum, fletcher64_stored, ObjPhys};
pub const APFS_MAGIC: u32 = 0x4253_5041;
const OBJECT_TYPE_FS: u16 = 0xd;
const OFF_MAGIC: usize = 32;
const OFF_FS_INDEX: usize = 36;
const OFF_FEATURES: usize = 40;
const OFF_RO_COMPAT_FEATURES: usize = 48;
const OFF_INCOMPAT_FEATURES: usize = 56;
const OFF_ROOT_TREE_TYPE: usize = 116;
const OFF_OMAP_OID: usize = 128;
const OFF_ROOT_TREE_OID: usize = 136;
const OFF_EXTENTREF_TREE_OID: usize = 144;
const OFF_SNAP_META_TREE_OID: usize = 152;
const OFF_VOL_UUID: usize = 240;
const OFF_FS_FLAGS: usize = 256;
const OFF_VOLNAME: usize = 704;
const VOLNAME_LEN: usize = 256;
const APSB_MIN_LEN: usize = OFF_VOLNAME + VOLNAME_LEN;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ApfsVolume {
oid: u64,
xid: u64,
fs_index: u32,
features: u64,
readonly_compatible_features: u64,
incompatible_features: u64,
root_tree_type: u32,
omap_oid: u64,
root_tree_oid: u64,
extentref_tree_oid: u64,
snap_meta_tree_oid: u64,
uuid: [u8; 16],
fs_flags: u64,
name: String,
}
impl ApfsVolume {
pub fn parse(block: &[u8]) -> crate::Result<Self> {
if block.len() < APSB_MIN_LEN {
return Err(crate::ApfsError::UnexpectedObjectType {
structure: "apfs_superblock",
expected: APFS_MAGIC,
found: 0,
});
}
let Some(hdr) = ObjPhys::parse(block) else {
return Err(crate::ApfsError::UnexpectedObjectType {
structure: "apfs_superblock",
expected: APFS_MAGIC,
found: 0,
}); };
if hdr.obj_type() != OBJECT_TYPE_FS {
return Err(crate::ApfsError::UnexpectedObjectType {
structure: "apfs_superblock",
expected: u32::from(OBJECT_TYPE_FS),
found: hdr.obj_type_raw,
});
}
let magic = crate::bytes::le_u32(block, OFF_MAGIC);
if magic != APFS_MAGIC {
return Err(crate::ApfsError::UnexpectedObjectType {
structure: "apfs_superblock",
expected: APFS_MAGIC,
found: magic,
});
}
let stored = fletcher64_stored(block);
let computed = fletcher64_checksum(block);
if stored != computed {
return Err(crate::ApfsError::ChecksumMismatch {
block: hdr.oid,
stored,
computed,
});
}
let name = decode_volname(block, OFF_VOLNAME, VOLNAME_LEN);
Ok(Self {
oid: hdr.oid,
xid: hdr.xid,
fs_index: crate::bytes::le_u32(block, OFF_FS_INDEX),
features: crate::bytes::le_u64(block, OFF_FEATURES),
readonly_compatible_features: crate::bytes::le_u64(block, OFF_RO_COMPAT_FEATURES),
incompatible_features: crate::bytes::le_u64(block, OFF_INCOMPAT_FEATURES),
root_tree_type: crate::bytes::le_u32(block, OFF_ROOT_TREE_TYPE),
omap_oid: crate::bytes::le_u64(block, OFF_OMAP_OID),
root_tree_oid: crate::bytes::le_u64(block, OFF_ROOT_TREE_OID),
extentref_tree_oid: crate::bytes::le_u64(block, OFF_EXTENTREF_TREE_OID),
snap_meta_tree_oid: crate::bytes::le_u64(block, OFF_SNAP_META_TREE_OID),
uuid: crate::bytes::arr::<16>(block, OFF_VOL_UUID),
fs_flags: crate::bytes::le_u64(block, OFF_FS_FLAGS),
name,
})
}
#[must_use]
pub fn oid(&self) -> u64 {
self.oid
}
#[must_use]
pub fn xid(&self) -> u64 {
self.xid
}
#[must_use]
pub fn fs_index(&self) -> u32 {
self.fs_index
}
#[must_use]
pub fn features(&self) -> u64 {
self.features
}
#[must_use]
pub fn readonly_compatible_features(&self) -> u64 {
self.readonly_compatible_features
}
#[must_use]
pub fn incompatible_features(&self) -> u64 {
self.incompatible_features
}
#[must_use]
pub fn root_tree_type(&self) -> u32 {
self.root_tree_type
}
#[must_use]
pub fn omap_oid(&self) -> u64 {
self.omap_oid
}
#[must_use]
pub fn with_omap_oid(mut self, omap_oid: u64) -> Self {
self.omap_oid = omap_oid;
self
}
#[must_use]
pub fn root_tree_oid(&self) -> u64 {
self.root_tree_oid
}
#[must_use]
pub fn extentref_tree_oid(&self) -> u64 {
self.extentref_tree_oid
}
#[must_use]
pub fn snap_meta_tree_oid(&self) -> u64 {
self.snap_meta_tree_oid
}
#[must_use]
pub fn uuid(&self) -> [u8; 16] {
self.uuid
}
#[must_use]
pub fn fs_flags(&self) -> u64 {
self.fs_flags
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
}
fn decode_volname(block: &[u8], offset: usize, len: usize) -> String {
let Some(field) = block.get(offset..offset + len) else {
return String::new(); };
let end = field.iter().position(|&b| b == 0).unwrap_or(field.len());
String::from_utf8_lossy(&field[..end]).into_owned()
}