#![forbid(unsafe_code)]
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
mod bytes;
pub mod btree;
pub mod checkpoint;
pub mod compression;
pub mod container;
pub mod dir;
pub mod encryption;
pub mod extent;
pub mod fsrecord;
pub mod fusion;
pub mod inode;
pub mod object;
pub mod omap;
pub mod reaper;
pub mod sealed;
pub mod snapshot;
pub mod spaceman;
pub mod volume;
pub mod xattr;
use std::io::{Read, Seek};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ApfsError {
#[error("i/o error: {0}")]
Io(#[from] std::io::Error),
#[error("no valid NXSB superblock found (checked {checked} checkpoint slots; last magic seen: {last_magic:#010x})")]
NoValidSuperblock { checked: usize, last_magic: u32 },
#[error("object checksum mismatch at block {block}: stored {stored:#018x}, computed {computed:#018x}")]
ChecksumMismatch {
block: u64,
stored: u64,
computed: u64,
},
#[error("omap could not resolve virtual oid {oid:#x} at xid {xid}")]
OmapUnresolved { oid: u64, xid: u64 },
#[error(
"unexpected object type in {structure}: expected {expected:#06x}, found {found:#010x}"
)]
UnexpectedObjectType {
structure: &'static str,
expected: u32,
found: u32,
},
#[error("unsupported Fusion container (tier-2 device present); Fusion addressing not yet implemented")]
UnsupportedFusion,
#[error("unsupported space-manager CAB tier (sm_cab_count = {count}); only inline CIB layout is implemented")]
UnsupportedSpacemanCab { count: u64 },
#[error("structural field out of range in {structure}: {field} = {value} (cap {cap})")]
FieldOutOfRange {
structure: &'static str,
field: &'static str,
value: u64,
cap: u64,
},
#[error("tree walk exceeded depth cap {cap} (possible cyclic object graph)")]
CycleGuard { cap: usize },
#[error("checkpoint {area} area is tree-backed; B-tree resolution not yet implemented")]
CheckpointTreeUnsupported { area: &'static str },
#[error("decmpfs decode failure: {0}")]
Decmpfs(&'static str),
}
pub type Result<T> = std::result::Result<T, ApfsError>;
pub struct ApfsContainer<R: Read + Seek> {
reader: R,
superblock: container::NxSuperblock,
live_superblock_paddr: u64,
checkpoint_mappings: Vec<checkpoint::CheckpointMapping>,
}
impl<R: Read + Seek> ApfsContainer<R> {
pub fn open(mut reader: R) -> Result<Self> {
let mut block0 = vec![0u8; container::NX_MINIMUM_BLOCK_SIZE as usize];
reader.seek(std::io::SeekFrom::Start(0))?;
match reader.read_exact(&mut block0) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
return Err(ApfsError::NoValidSuperblock {
checked: 0,
last_magic: 0,
});
}
Err(e) => return Err(e.into()),
}
let bootstrap = container::NxSuperblock::parse(&block0)?;
if fusion::is_fusion(&bootstrap) {
return Err(ApfsError::UnsupportedFusion);
}
let live = checkpoint::resolve_live_checkpoint(&mut reader, &bootstrap)?;
let block_size = bootstrap.block_size as usize;
let mut buf = vec![0u8; block_size];
let offset = live.superblock_paddr.saturating_mul(block_size as u64);
reader.seek(std::io::SeekFrom::Start(offset))?;
reader.read_exact(&mut buf)?;
let superblock = container::NxSuperblock::parse(&buf)?;
Ok(Self {
reader,
superblock,
live_superblock_paddr: live.superblock_paddr,
checkpoint_mappings: live.mappings,
})
}
#[must_use]
pub fn checkpoint_mappings(&self) -> &[checkpoint::CheckpointMapping] {
&self.checkpoint_mappings
}
#[must_use]
pub fn spaceman_paddr(&self) -> Option<u64> {
self.resolve_ephemeral(self.superblock.spaceman_oid)
}
#[must_use]
pub fn reaper_paddr(&self) -> Option<u64> {
self.resolve_ephemeral(self.superblock.reaper_oid)
}
fn resolve_ephemeral(&self, oid: u64) -> Option<u64> {
self.checkpoint_mappings
.iter()
.find(|m| m.oid == oid)
.map(|m| m.paddr)
}
#[must_use]
pub fn superblock(&self) -> &container::NxSuperblock {
&self.superblock
}
#[must_use]
pub fn live_superblock_paddr(&self) -> u64 {
self.live_superblock_paddr
}
pub fn volume_superblock_addrs(&mut self) -> Result<Vec<u64>> {
let block_size = self.superblock.block_size;
if !(container::NX_MINIMUM_BLOCK_SIZE..=container::NX_MAXIMUM_BLOCK_SIZE)
.contains(&block_size)
{
return Err(ApfsError::FieldOutOfRange {
structure: "nx_superblock",
field: "nx_block_size",
value: u64::from(block_size),
cap: u64::from(container::NX_MAXIMUM_BLOCK_SIZE),
});
}
let block_size = block_size as usize;
let mut buf = vec![0u8; block_size];
let omap_off = self.superblock.omap_oid.saturating_mul(block_size as u64);
self.reader.seek(std::io::SeekFrom::Start(omap_off))?;
self.reader.read_exact(&mut buf)?;
let omap = omap::ObjectMap::parse(&buf)?;
let xid = self.superblock.xid;
let mut addrs = Vec::with_capacity(self.superblock.fs_oids.len());
for &fs_oid in &self.superblock.fs_oids {
let entry = omap.resolve(&mut self.reader, fs_oid, xid, block_size)?;
addrs.push(entry.paddr);
}
Ok(addrs)
}
#[must_use]
pub fn into_reader(self) -> R {
self.reader
}
}