#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
#[derive(Debug)]
pub enum Error {
NotApm,
TooShort { need: usize, got: usize },
Io(std::io::Error),
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::NotApm => f.write_str("not an Apple Partition Map (missing ER/PM signature)"),
Error::TooShort { need, got } => {
write!(f, "buffer too short: need {need} bytes, got {got}")
}
Error::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::Io(e)
}
}
const SIG_DDM: &[u8; 2] = b"ER";
const SIG_PM: &[u8; 2] = b"PM";
const MAX_PARTITIONS: u32 = 256;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ApmPartition {
pub name: String,
pub type_name: String,
pub start_block: u32,
pub block_count: u32,
pub map_count: u32,
pub status: u32,
}
impl ApmPartition {
#[must_use]
pub fn end_block(&self) -> u32 {
self.start_block
.saturating_add(self.block_count)
.saturating_sub(1)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ApplePartitionMap {
pub block_size: u32,
pub device_block_count: u32,
pub partitions: Vec<ApmPartition>,
}
impl ApplePartitionMap {
#[must_use]
pub fn hfs_partition(&self) -> Option<&ApmPartition> {
self.partitions
.iter()
.find(|p| p.type_name.starts_with("Apple_HFS"))
}
}
#[must_use]
pub fn parse(data: &[u8]) -> Option<ApplePartitionMap> {
if data.len() < 512 || data.get(0..2)? != SIG_DDM {
return None;
}
let block_size = u32::from(be16(data.get(2..4)?));
let device_block_count = be32(data.get(4..8)?);
let bs = block_size as usize;
if bs == 0 {
return None;
}
let first = bs;
if data.get(first..first + 2)? != SIG_PM {
return None;
}
let map_count = be32(data.get(first + 4..first + 8)?).min(MAX_PARTITIONS);
let mut partitions = Vec::new();
for i in 0..map_count {
let off = bs * (1 + i as usize);
let entry = match data.get(off..off + 92) {
Some(e) if &e[0..2] == SIG_PM => e,
_ => break,
};
partitions.push(ApmPartition {
map_count: be32(&entry[4..8]),
start_block: be32(&entry[8..12]),
block_count: be32(&entry[12..16]),
name: cstr(&entry[16..48]),
type_name: cstr(&entry[48..80]),
status: be32(&entry[88..92]),
});
}
Some(ApplePartitionMap {
block_size,
device_block_count,
partitions,
})
}
fn cstr(bytes: &[u8]) -> String {
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
bytes[..end].iter().map(|&b| b as char).collect()
}
fn be16(b: &[u8]) -> u16 {
u16::from_be_bytes([
b.first().copied().unwrap_or(0),
b.get(1).copied().unwrap_or(0),
])
}
fn be32(b: &[u8]) -> u32 {
u32::from_be_bytes([
b.first().copied().unwrap_or(0),
b.get(1).copied().unwrap_or(0),
b.get(2).copied().unwrap_or(0),
b.get(3).copied().unwrap_or(0),
])
}