use crate::Result;
use crate::block::BlockDevice;
use super::{Partition, PartitionKind, PartitionTable};
const APM_BLOCK: u64 = 512;
const DDM_SIG: &[u8; 2] = b"ER";
const PM_SIG: &[u8; 2] = b"PM";
const MAX_ENTRIES: u32 = 256;
#[derive(Debug, Clone)]
pub struct Apm {
partitions: Vec<Partition>,
}
impl Apm {
pub fn probe(dev: &mut dyn BlockDevice) -> bool {
if dev.total_size() < 1024 {
return false;
}
let mut head = [0u8; 2];
if dev.read_at(0, &mut head).is_err() || &head != DDM_SIG {
return false;
}
if dev.read_at(APM_BLOCK, &mut head).is_err() || &head != PM_SIG {
return false;
}
true
}
pub fn read(dev: &mut dyn BlockDevice) -> Result<Self> {
let total = dev.total_size();
let mut first = [0u8; 512];
dev.read_at(APM_BLOCK, &mut first)?;
if &first[0..2] != PM_SIG {
return Err(crate::Error::InvalidImage(
"apm: no PM signature at block 1".into(),
));
}
let map_blk_cnt = be32(&first, 4).min(MAX_ENTRIES);
let mut partitions = Vec::new();
for i in 0..map_blk_cnt {
let off = (u64::from(i) + 1) * APM_BLOCK;
if off + 512 > total {
break;
}
let mut e = [0u8; 512];
dev.read_at(off, &mut e)?;
if &e[0..2] != PM_SIG {
break;
}
let start = u64::from(be32(&e, 8));
let count = u64::from(be32(&e, 12));
let name = c_str(&e[16..48]);
let ptype = c_str(&e[48..80]);
let mut part = Partition::new(start, count, PartitionKind::Apm(ptype));
if !name.is_empty() {
part.name = Some(name);
}
partitions.push(part);
}
Ok(Apm { partitions })
}
}
impl PartitionTable for Apm {
fn write(&self, _dev: &mut dyn BlockDevice) -> Result<()> {
Err(crate::Error::Unsupported(
"apm: writing an Apple Partition Map is not supported".into(),
))
}
fn partitions(&self) -> &[Partition] {
&self.partitions
}
}
fn be32(b: &[u8], off: usize) -> u32 {
u32::from_be_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
}
fn c_str(b: &[u8]) -> String {
let end = b.iter().position(|&c| c == 0).unwrap_or(b.len());
String::from_utf8_lossy(&b[..end]).into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::MemoryBackend;
fn build_apm(entries: &[(u32, u32, &str, &str)]) -> MemoryBackend {
let n = entries.len() as u32;
let max_end = entries
.iter()
.map(|(s, c, _, _)| u64::from(*s) + u64::from(*c))
.max()
.unwrap_or(0);
let total = (max_end.max(u64::from(n) + 1)) * APM_BLOCK;
let mut dev = MemoryBackend::new(total);
let mut ddm = [0u8; 512];
ddm[0..2].copy_from_slice(DDM_SIG);
ddm[2..4].copy_from_slice(&512u16.to_be_bytes());
ddm[4..8].copy_from_slice(&((total / APM_BLOCK) as u32).to_be_bytes());
dev.write_at(0, &ddm).unwrap();
for (i, (start, count, name, ptype)) in entries.iter().enumerate() {
let mut e = [0u8; 512];
e[0..2].copy_from_slice(PM_SIG);
e[4..8].copy_from_slice(&n.to_be_bytes()); e[8..12].copy_from_slice(&start.to_be_bytes());
e[12..16].copy_from_slice(&count.to_be_bytes());
let nb = name.as_bytes();
e[16..16 + nb.len()].copy_from_slice(nb);
let tb = ptype.as_bytes();
e[48..48 + tb.len()].copy_from_slice(tb);
dev.write_at((i as u64 + 1) * APM_BLOCK, &e).unwrap();
}
dev
}
#[test]
fn probe_and_parse_three_partitions() {
let mut dev = build_apm(&[
(1, 63, "Apple", "Apple_partition_map"),
(64, 800, "MacOS", "Apple_HFS"),
(864, 100, "Extra", "Apple_Free"),
]);
assert!(Apm::probe(&mut dev));
let apm = Apm::read(&mut dev).unwrap();
let parts = apm.partitions();
assert_eq!(parts.len(), 3);
assert_eq!(parts[1].start_lba, 64);
assert_eq!(parts[1].size_lba, 800);
assert_eq!(parts[1].name.as_deref(), Some("MacOS"));
assert_eq!(parts[1].kind, PartitionKind::Apm("Apple_HFS".to_string()));
}
#[test]
fn probe_rejects_bare_volume() {
let mut dev = MemoryBackend::new(2048);
assert!(!Apm::probe(&mut dev));
assert!(Apm::read(&mut dev).is_err());
}
#[test]
fn slice_targets_the_hfs_partition() {
let mut dev = build_apm(&[
(1, 63, "Apple", "Apple_partition_map"),
(64, 8, "MacOS", "Apple_HFS"),
]);
let apm = Apm::read(&mut dev).unwrap();
let sliced = super::super::slice_partition(&apm, &mut dev, 1).unwrap();
assert_eq!(sliced.total_size(), 8 * 512);
}
#[test]
fn write_is_unsupported() {
let mut dev = build_apm(&[(1, 63, "Apple", "Apple_partition_map")]);
let apm = Apm::read(&mut dev).unwrap();
let mut out = MemoryBackend::new(4096);
assert!(apm.write(&mut out).is_err());
}
}