use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{Cursor, Read, Seek, SeekFrom};
use crate::error::{ApfsError, Result};
use crate::fletcher;
use crate::object::{OBJECT_TYPE_NX_SUPERBLOCK, ObjectHeader};
pub const NX_MAGIC: u32 = 0x4253584E;
pub const APSB_MAGIC: u32 = 0x42535041;
pub const NX_MAX_FILE_SYSTEMS: usize = 100;
#[derive(Debug, Clone)]
pub struct NxSuperblock {
pub header: ObjectHeader,
pub magic: u32,
pub block_size: u32,
pub block_count: u64,
pub features: u64,
pub readonly_compatible_features: u64,
pub incompatible_features: u64,
pub uuid: [u8; 16],
pub next_oid: u64,
pub next_xid: u64,
pub xp_desc_blocks: u32,
pub xp_data_blocks: u32,
pub xp_desc_base: u64, pub xp_data_base: u64,
pub xp_desc_next: u32,
pub xp_data_next: u32,
pub xp_desc_index: u32,
pub xp_desc_len: u32,
pub xp_data_index: u32,
pub xp_data_len: u32,
pub spaceman_oid: u64,
pub omap_oid: u64, pub reaper_oid: u64,
pub max_file_systems: u32,
pub fs_oids: Vec<u64>, }
impl NxSuperblock {
pub fn parse(block: &[u8]) -> Result<Self> {
let header = ObjectHeader::parse(block)?;
let mut cursor = Cursor::new(block);
cursor.set_position(ObjectHeader::SIZE as u64);
let magic = cursor.read_u32::<LittleEndian>()?;
if magic != NX_MAGIC {
return Err(ApfsError::InvalidMagic(magic));
}
let block_size = cursor.read_u32::<LittleEndian>()?;
let block_count = cursor.read_u64::<LittleEndian>()?;
let features = cursor.read_u64::<LittleEndian>()?;
let readonly_compatible_features = cursor.read_u64::<LittleEndian>()?;
let incompatible_features = cursor.read_u64::<LittleEndian>()?;
let mut uuid = [0u8; 16];
std::io::Read::read_exact(&mut cursor, &mut uuid)?;
let next_oid = cursor.read_u64::<LittleEndian>()?;
let next_xid = cursor.read_u64::<LittleEndian>()?;
let xp_desc_blocks = cursor.read_u32::<LittleEndian>()?;
let xp_data_blocks = cursor.read_u32::<LittleEndian>()?;
let xp_desc_base = cursor.read_u64::<LittleEndian>()?;
let xp_data_base = cursor.read_u64::<LittleEndian>()?;
let xp_desc_next = cursor.read_u32::<LittleEndian>()?;
let xp_data_next = cursor.read_u32::<LittleEndian>()?;
let xp_desc_index = cursor.read_u32::<LittleEndian>()?;
let xp_desc_len = cursor.read_u32::<LittleEndian>()?;
let xp_data_index = cursor.read_u32::<LittleEndian>()?;
let xp_data_len = cursor.read_u32::<LittleEndian>()?;
let spaceman_oid = cursor.read_u64::<LittleEndian>()?;
let omap_oid = cursor.read_u64::<LittleEndian>()?;
let reaper_oid = cursor.read_u64::<LittleEndian>()?;
let _test_type = cursor.read_u32::<LittleEndian>()?; let max_file_systems = cursor.read_u32::<LittleEndian>()?;
let fs_count = std::cmp::min(max_file_systems as usize, NX_MAX_FILE_SYSTEMS);
let mut fs_oids = Vec::with_capacity(fs_count);
for _ in 0..fs_count {
fs_oids.push(cursor.read_u64::<LittleEndian>()?);
}
Ok(NxSuperblock {
header,
magic,
block_size,
block_count,
features,
readonly_compatible_features,
incompatible_features,
uuid,
next_oid,
next_xid,
xp_desc_blocks,
xp_data_blocks,
xp_desc_base,
xp_data_base,
xp_desc_next,
xp_data_next,
xp_desc_index,
xp_desc_len,
xp_data_index,
xp_data_len,
spaceman_oid,
omap_oid,
reaper_oid,
max_file_systems,
fs_oids,
})
}
}
#[derive(Debug, Clone)]
pub struct ApfsSuperblock {
pub header: ObjectHeader,
pub magic: u32,
pub fs_index: u32,
pub features: u64,
pub readonly_compatible_features: u64,
pub incompatible_features: u64,
pub unmount_time: u64,
pub fs_reserve_block_count: u64,
pub fs_quota_block_count: u64,
pub fs_alloc_count: u64,
pub root_tree_type: u32,
pub extentref_tree_type: u32,
pub snap_meta_tree_type: u32,
pub omap_oid: u64, pub root_tree_oid: u64, pub extentref_tree_oid: u64,
pub snap_meta_tree_oid: u64,
pub revert_to_xid: u64,
pub revert_to_sblock_oid: u64,
pub next_obj_id: u64,
pub num_files: u64,
pub num_directories: u64,
pub num_symlinks: u64,
pub num_other_fsobjects: u64,
pub num_snapshots: u64,
pub total_blocks_alloced: u64,
pub total_blocks_freed: u64,
pub uuid: [u8; 16],
pub last_mod_time: u64,
pub fs_flags: u64,
pub volume_name: String,
}
impl ApfsSuperblock {
pub fn parse(block: &[u8]) -> Result<Self> {
let header = ObjectHeader::parse(block)?;
let mut cursor = Cursor::new(block);
cursor.set_position(ObjectHeader::SIZE as u64);
let magic = cursor.read_u32::<LittleEndian>()?;
if magic != APSB_MAGIC {
return Err(ApfsError::InvalidMagic(magic));
}
let fs_index = cursor.read_u32::<LittleEndian>()?;
let features = cursor.read_u64::<LittleEndian>()?;
let readonly_compatible_features = cursor.read_u64::<LittleEndian>()?;
let incompatible_features = cursor.read_u64::<LittleEndian>()?;
let unmount_time = cursor.read_u64::<LittleEndian>()?;
let fs_reserve_block_count = cursor.read_u64::<LittleEndian>()?;
let fs_quota_block_count = cursor.read_u64::<LittleEndian>()?;
let fs_alloc_count = cursor.read_u64::<LittleEndian>()?;
let mut _skip = [0u8; 20];
std::io::Read::read_exact(&mut cursor, &mut _skip)?;
let root_tree_type = cursor.read_u32::<LittleEndian>()?;
let extentref_tree_type = cursor.read_u32::<LittleEndian>()?;
let snap_meta_tree_type = cursor.read_u32::<LittleEndian>()?;
let omap_oid = cursor.read_u64::<LittleEndian>()?;
let root_tree_oid = cursor.read_u64::<LittleEndian>()?;
let extentref_tree_oid = cursor.read_u64::<LittleEndian>()?;
let snap_meta_tree_oid = cursor.read_u64::<LittleEndian>()?;
let revert_to_xid = cursor.read_u64::<LittleEndian>()?;
let revert_to_sblock_oid = cursor.read_u64::<LittleEndian>()?;
let next_obj_id = cursor.read_u64::<LittleEndian>()?;
let num_files = cursor.read_u64::<LittleEndian>()?;
let num_directories = cursor.read_u64::<LittleEndian>()?;
let num_symlinks = cursor.read_u64::<LittleEndian>()?;
let num_other_fsobjects = cursor.read_u64::<LittleEndian>()?;
let num_snapshots = cursor.read_u64::<LittleEndian>()?;
let total_blocks_alloced = cursor.read_u64::<LittleEndian>()?;
let total_blocks_freed = cursor.read_u64::<LittleEndian>()?;
let mut uuid = [0u8; 16];
std::io::Read::read_exact(&mut cursor, &mut uuid)?;
let last_mod_time = cursor.read_u64::<LittleEndian>()?;
let fs_flags = cursor.read_u64::<LittleEndian>()?;
let mut _formatted_by = [0u8; 48];
std::io::Read::read_exact(&mut cursor, &mut _formatted_by)?;
let mut _modified_by = [0u8; 48];
for _ in 0..8 {
std::io::Read::read_exact(&mut cursor, &mut _modified_by)?;
}
let mut name_buf = [0u8; 256];
std::io::Read::read_exact(&mut cursor, &mut name_buf)?;
let volume_name = {
let nul_pos = name_buf.iter().position(|&b| b == 0).unwrap_or(256);
String::from_utf8_lossy(&name_buf[..nul_pos]).to_string()
};
Ok(ApfsSuperblock {
header,
magic,
fs_index,
features,
readonly_compatible_features,
incompatible_features,
unmount_time,
fs_reserve_block_count,
fs_quota_block_count,
fs_alloc_count,
root_tree_type,
extentref_tree_type,
snap_meta_tree_type,
omap_oid,
root_tree_oid,
extentref_tree_oid,
snap_meta_tree_oid,
revert_to_xid,
revert_to_sblock_oid,
next_obj_id,
num_files,
num_directories,
num_symlinks,
num_other_fsobjects,
num_snapshots,
total_blocks_alloced,
total_blocks_freed,
uuid,
last_mod_time,
fs_flags,
volume_name,
})
}
}
pub fn find_latest_nxsb<R: Read + Seek>(
reader: &mut R,
nxsb: &NxSuperblock,
) -> Result<NxSuperblock> {
let block_size = nxsb.block_size;
let base = nxsb.xp_desc_base;
let count = nxsb.xp_desc_blocks;
let mut best: Option<NxSuperblock> = None;
let mut best_xid: u64 = 0;
for i in 0..count as u64 {
let block_num = base + i;
let offset = block_num * block_size as u64;
reader.seek(SeekFrom::Start(offset))?;
let mut block = vec![0u8; block_size as usize];
if reader.read_exact(&mut block).is_err() {
continue;
}
if !fletcher::verify_object(&block) {
continue;
}
let header = match ObjectHeader::parse(&block) {
Ok(h) => h,
Err(_) => continue,
};
if header.object_type() != OBJECT_TYPE_NX_SUPERBLOCK {
continue;
}
let candidate = match NxSuperblock::parse(&block) {
Ok(sb) => sb,
Err(_) => continue,
};
if candidate.magic != NX_MAGIC {
continue;
}
if candidate.header.xid > best_xid {
best_xid = candidate.header.xid;
best = Some(candidate);
}
}
match best {
Some(sb) if sb.header.xid > nxsb.header.xid => Ok(sb),
_ => Ok(nxsb.clone()),
}
}
pub fn read_nxsb<R: Read + Seek>(reader: &mut R) -> Result<NxSuperblock> {
reader.seek(SeekFrom::Start(0))?;
let mut block = vec![0u8; 4096];
reader.read_exact(&mut block)?;
if !fletcher::verify_object(&block) {
return Err(ApfsError::InvalidChecksum);
}
let nxsb = NxSuperblock::parse(&block)?;
if nxsb.block_size != 4096 {
reader.seek(SeekFrom::Start(0))?;
let mut block = vec![0u8; nxsb.block_size as usize];
reader.read_exact(&mut block)?;
if !fletcher::verify_object(&block) {
return Err(ApfsError::InvalidChecksum);
}
return NxSuperblock::parse(&block);
}
Ok(nxsb)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::BufReader;
fn open_appfs() -> BufReader<std::fs::File> {
let file = std::fs::File::open("../tests/appfs.raw").unwrap();
BufReader::new(file)
}
#[test]
#[ignore]
fn test_parse_nxsb() {
let mut reader = open_appfs();
let nxsb = read_nxsb(&mut reader).unwrap();
assert_eq!(nxsb.magic, NX_MAGIC);
assert_eq!(nxsb.block_size, 4096);
assert!(nxsb.block_count > 0);
let file_size = reader.seek(SeekFrom::End(0)).unwrap();
let expected_size = nxsb.block_count * nxsb.block_size as u64;
assert_eq!(
file_size, expected_size,
"File size {} should match block_count({}) * block_size({})",
file_size, nxsb.block_count, nxsb.block_size
);
}
#[test]
#[ignore]
fn test_checkpoint_scan() {
let mut reader = open_appfs();
let nxsb = read_nxsb(&mut reader).unwrap();
let latest = find_latest_nxsb(&mut reader, &nxsb).unwrap();
assert!(
latest.header.xid >= nxsb.header.xid,
"Latest xid {} should be >= block 0 xid {}",
latest.header.xid,
nxsb.header.xid
);
}
#[test]
#[ignore]
fn test_volume_superblock() {
let mut reader = open_appfs();
let nxsb = read_nxsb(&mut reader).unwrap();
let latest = find_latest_nxsb(&mut reader, &nxsb).unwrap();
assert!(
latest.fs_oids.iter().any(|&o| o != 0),
"Should have at least one volume"
);
let omap_block =
crate::object::read_block(&mut reader, latest.omap_oid, latest.block_size).unwrap();
let omap_header = crate::object::ObjectHeader::parse(&omap_block).unwrap();
assert_ne!(omap_header.object_type(), 0);
let mut cursor = Cursor::new(&omap_block[32..]);
let _om_flags = cursor.read_u32::<LittleEndian>().unwrap();
let _om_snap_count = cursor.read_u32::<LittleEndian>().unwrap();
let _om_tree_type = cursor.read_u32::<LittleEndian>().unwrap();
let _om_snap_tree_type = cursor.read_u32::<LittleEndian>().unwrap();
let om_tree_oid = cursor.read_u64::<LittleEndian>().unwrap();
assert!(om_tree_oid > 0);
}
#[test]
fn test_nxsb_invalid_magic() {
let mut block = vec![0u8; 4096];
block[24..28].copy_from_slice(&0x01u32.to_le_bytes()); block[32..36].copy_from_slice(&0xDEADBEEFu32.to_le_bytes());
let result = NxSuperblock::parse(&block);
assert!(matches!(result, Err(ApfsError::InvalidMagic(0xDEADBEEF))));
}
}