use crate::container::NX_MAXIMUM_BLOCK_SIZE;
use crate::object::{fletcher64_checksum, fletcher64_stored, ObjPhys};
const OBJECT_TYPE_NX_SUPERBLOCK: u16 = 0x1;
const OBJECT_TYPE_CHECKPOINT_MAP: u16 = 0xc;
const CPM_COUNT_OFF: usize = 36; const CPM_MAP_OFF: usize = 40; const CHECKPOINT_MAPPING_LEN: usize = 40;
const MAX_CPM_COUNT: u32 = 4096;
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct CheckpointMapping {
pub oid: u64,
pub paddr: u64,
pub obj_type: u32,
pub subtype: u32,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct LiveCheckpoint {
pub superblock_paddr: u64,
pub xid: u64,
pub mappings: Vec<CheckpointMapping>,
}
pub fn resolve_live_checkpoint<R: std::io::Read + std::io::Seek>(
reader: &mut R,
bootstrap: &crate::container::NxSuperblock,
) -> crate::Result<LiveCheckpoint> {
if bootstrap.xp_desc_is_tree() {
return Err(crate::ApfsError::CheckpointTreeUnsupported { area: "descriptor" });
}
let block_size = bootstrap.block_size;
if !(crate::container::NX_MINIMUM_BLOCK_SIZE..=NX_MAXIMUM_BLOCK_SIZE).contains(&block_size) {
return Err(crate::ApfsError::FieldOutOfRange {
structure: "nx_superblock",
field: "nx_block_size",
value: u64::from(block_size),
cap: u64::from(NX_MAXIMUM_BLOCK_SIZE),
});
}
let block_size = block_size as usize;
let mut buf = vec![0u8; block_size];
let desc_base = bootstrap.xp_desc_base;
let desc_blocks = bootstrap.xp_desc_blocks;
let mut best: Option<(u64, u64)> = None; let mut checked = 0usize;
let mut last_magic = 0u32;
for i in 0..u64::from(desc_blocks) {
let paddr = desc_base + i;
if !read_block(reader, paddr, block_size, &mut buf)? {
continue; }
checked += 1;
let Some(hdr) = ObjPhys::parse(&buf) else {
continue; };
if hdr.obj_type() != OBJECT_TYPE_NX_SUPERBLOCK {
continue;
}
let magic = crate::bytes::le_u32(&buf, 32);
last_magic = magic;
if magic != crate::container::NX_MAGIC {
continue; }
if fletcher64_stored(&buf) != fletcher64_checksum(&buf) {
continue;
}
if best.is_none_or(|(bx, _)| hdr.xid > bx) {
best = Some((hdr.xid, paddr));
}
}
let Some((xid, superblock_paddr)) = best else {
return Err(crate::ApfsError::NoValidSuperblock {
checked,
last_magic,
});
};
let mut mappings = Vec::new();
for i in 0..u64::from(desc_blocks) {
let paddr = desc_base + i;
if !read_block(reader, paddr, block_size, &mut buf)? {
continue; }
let Some(hdr) = ObjPhys::parse(&buf) else {
continue; };
if hdr.obj_type() != OBJECT_TYPE_CHECKPOINT_MAP || hdr.xid != xid {
continue;
}
if fletcher64_stored(&buf) != fletcher64_checksum(&buf) {
continue; }
let count = crate::bytes::le_u32(&buf, CPM_COUNT_OFF).min(MAX_CPM_COUNT);
for m in 0..count as usize {
let off = CPM_MAP_OFF + m * CHECKPOINT_MAPPING_LEN;
mappings.push(CheckpointMapping {
obj_type: crate::bytes::le_u32(&buf, off),
subtype: crate::bytes::le_u32(&buf, off + 4),
oid: crate::bytes::le_u64(&buf, off + 24),
paddr: crate::bytes::le_u64(&buf, off + 32),
});
}
}
Ok(LiveCheckpoint {
superblock_paddr,
xid,
mappings,
})
}
fn read_block<R: std::io::Read + std::io::Seek>(
reader: &mut R,
paddr: u64,
block_size: usize,
buf: &mut [u8],
) -> crate::Result<bool> {
let Some(offset) = paddr.checked_mul(block_size as u64) else {
return Ok(false); };
reader.seek(std::io::SeekFrom::Start(offset))?;
match reader.read_exact(buf) {
Ok(()) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(false),
Err(e) => Err(e.into()),
}
}