1#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
19
20#[cfg(feature = "vfs")]
21pub mod vfs;
22
23#[derive(Debug)]
25pub enum Error {
26 NotApm,
29 TooShort { need: usize, got: usize },
31 Io(std::io::Error),
33}
34
35impl core::fmt::Display for Error {
36 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37 match self {
38 Error::NotApm => f.write_str("not an Apple Partition Map (missing ER/PM signature)"),
39 Error::TooShort { need, got } => {
40 write!(f, "buffer too short: need {need} bytes, got {got}")
41 }
42 Error::Io(e) => write!(f, "I/O error: {e}"),
43 }
44 }
45}
46
47impl std::error::Error for Error {
48 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
49 match self {
50 Error::Io(e) => Some(e),
51 _ => None,
52 }
53 }
54}
55
56impl From<std::io::Error> for Error {
57 fn from(e: std::io::Error) -> Self {
58 Error::Io(e)
59 }
60}
61
62const SIG_DDM: &[u8; 2] = b"ER";
64const SIG_PM: &[u8; 2] = b"PM";
66const MAX_PARTITIONS: u32 = 256;
68
69#[derive(Debug, Clone, PartialEq, Eq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize))]
72pub struct ApmPartition {
73 pub name: String,
75 pub type_name: String,
77 pub start_block: u32,
79 pub block_count: u32,
81 pub map_count: u32,
84 pub status: u32,
86}
87
88impl ApmPartition {
89 #[must_use]
91 pub fn end_block(&self) -> u32 {
92 self.start_block
93 .saturating_add(self.block_count)
94 .saturating_sub(1)
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize))]
101pub struct ApplePartitionMap {
102 pub block_size: u32,
104 pub device_block_count: u32,
106 pub partitions: Vec<ApmPartition>,
108}
109
110impl ApplePartitionMap {
111 #[must_use]
113 pub fn hfs_partition(&self) -> Option<&ApmPartition> {
114 self.partitions
115 .iter()
116 .find(|p| p.type_name.starts_with("Apple_HFS"))
117 }
118}
119
120#[must_use]
124pub fn parse(data: &[u8]) -> Option<ApplePartitionMap> {
125 if data.len() < 512 || data.get(0..2)? != SIG_DDM {
126 return None;
127 }
128 let block_size = u32::from(be16(data.get(2..4)?));
129 let device_block_count = be32(data.get(4..8)?);
130 let bs = block_size as usize;
131 if bs == 0 {
132 return None;
133 }
134 let first = bs;
136 if data.get(first..first + 2)? != SIG_PM {
137 return None;
138 }
139 let map_count = be32(data.get(first + 4..first + 8)?).min(MAX_PARTITIONS);
140
141 let mut partitions = Vec::new();
142 for i in 0..map_count {
143 let off = bs * (1 + i as usize);
144 let entry = match data.get(off..off + 92) {
145 Some(e) if &e[0..2] == SIG_PM => e,
146 _ => break,
147 };
148 partitions.push(ApmPartition {
149 map_count: be32(&entry[4..8]),
150 start_block: be32(&entry[8..12]),
151 block_count: be32(&entry[12..16]),
152 name: cstr(&entry[16..48]),
153 type_name: cstr(&entry[48..80]),
154 status: be32(&entry[88..92]),
155 });
156 }
157 Some(ApplePartitionMap {
158 block_size,
159 device_block_count,
160 partitions,
161 })
162}
163
164fn cstr(bytes: &[u8]) -> String {
166 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
167 bytes[..end].iter().map(|&b| b as char).collect()
168}
169
170fn be16(b: &[u8]) -> u16 {
173 u16::from_be_bytes([
174 b.first().copied().unwrap_or(0),
175 b.get(1).copied().unwrap_or(0),
176 ])
177}
178
179fn be32(b: &[u8]) -> u32 {
182 u32::from_be_bytes([
183 b.first().copied().unwrap_or(0),
184 b.get(1).copied().unwrap_or(0),
185 b.get(2).copied().unwrap_or(0),
186 b.get(3).copied().unwrap_or(0),
187 ])
188}