1use anyhow::{Error, anyhow, ensure};
6use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
7
8const MAX_PARTITION_ENTRIES: u32 = 128;
9
10pub const GPT_SIGNATURE: [u8; 8] = [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54];
11pub const GPT_REVISION: u32 = 0x10000;
12pub const GPT_HEADER_SIZE: usize = 92;
13
14#[derive(Clone, Debug, Eq, PartialEq, Immutable, IntoBytes, KnownLayout, FromBytes)]
16#[repr(C)]
17pub struct Header {
18 pub signature: [u8; 8],
20 pub revision: u32,
22 pub header_size: u32,
24 pub crc32: u32,
26 pub reserved: u32,
28 pub current_lba: u64,
30 pub backup_lba: u64,
32 pub first_usable: u64,
34 pub last_usable: u64,
36 pub disk_guid: [u8; 16],
38 pub part_start: u64,
40 pub num_parts: u32,
42 pub part_size: u32,
44 pub crc32_parts: u32,
46 zerocopy_padding: u32,
49}
50
51impl Header {
52 pub fn new(block_count: u64, block_size: u32, num_parts: u32) -> Result<Self, Error> {
53 ensure!(block_size > 0 && block_size.is_power_of_two(), "Invalid block size");
54 let bs = block_size as u64;
55
56 let part_size = std::mem::size_of::<PartitionTableEntry>();
57 let partition_table_len = num_parts as u64 * part_size as u64;
58 let partition_table_blocks = partition_table_len.checked_next_multiple_of(bs).unwrap() / bs;
59
60 ensure!(block_count > 1 + 2 * (1 + partition_table_blocks), "Too few blocks");
63
64 let mut this = Self {
65 signature: GPT_SIGNATURE,
66 revision: GPT_REVISION,
67 header_size: GPT_HEADER_SIZE as u32,
68 crc32: 0,
69 reserved: 0,
70 current_lba: 1,
71 backup_lba: block_count - 1,
72 first_usable: 2 + partition_table_blocks,
73 last_usable: block_count - (2 + partition_table_blocks),
74 disk_guid: uuid::Uuid::new_v4().into_bytes(),
75 part_start: 2,
76 num_parts,
77 part_size: part_size as u32,
78 crc32_parts: 0,
79 zerocopy_padding: 0,
80 };
81 this.update_checksum();
82 Ok(this)
83 }
84
85 pub fn compute_checksum(&self) -> u32 {
87 let mut header_copy = self.clone();
88 header_copy.crc32 = 0;
89 crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC)
90 .checksum(&header_copy.as_bytes()[..GPT_HEADER_SIZE])
91 }
92
93 fn update_checksum(&mut self) {
94 self.crc32 = 0;
95 let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC)
96 .checksum(&self.as_bytes()[..GPT_HEADER_SIZE]);
97 self.crc32 = crc;
98 }
99
100 pub fn ensure_integrity(&self, block_count: u64, block_size: u64) -> Result<(), Error> {
102 ensure!(self.signature == GPT_SIGNATURE, "Bad signature {:x?}", self.signature);
103 ensure!(self.revision == GPT_REVISION, "Bad revision {:x}", self.revision);
104 ensure!(
105 self.header_size as usize == GPT_HEADER_SIZE,
106 "Bad header size {}",
107 self.header_size
108 );
109
110 ensure!(self.crc32 == self.compute_checksum(), "Invalid header checksum");
113
114 ensure!(self.num_parts <= MAX_PARTITION_ENTRIES, "Invalid num_parts {}", self.num_parts);
115 ensure!(
116 self.part_size as usize == std::mem::size_of::<PartitionTableEntry>(),
117 "Invalid part_size {}",
118 self.part_size
119 );
120 let partition_table_blocks = (self
121 .num_parts
122 .checked_mul(self.part_size)
123 .and_then(|v| v.checked_next_multiple_of(block_size as u32))
124 .ok_or_else(|| anyhow!("Partition table size overflow"))?
125 as u64)
126 / block_size;
127 ensure!(partition_table_blocks < block_count, "Invalid partition table size");
128
129 ensure!(
133 self.current_lba == 1 || self.current_lba == block_count - 1,
134 "Invalid current_lba {}",
135 self.current_lba
136 );
137 if self.current_lba == 1 {
138 ensure!(
143 self.backup_lba < block_count,
144 "backup_lba out of bounds {} (block_count {block_count})",
145 self.backup_lba,
146 );
147 } else {
148 ensure!(self.backup_lba == 1, "Invalid backup_lba {}", self.backup_lba);
149 }
150 let (first_lba, second_lba) = if self.current_lba == 1 {
151 (self.current_lba, self.backup_lba)
152 } else {
153 (self.backup_lba, self.current_lba)
154 };
155
156 ensure!(
157 self.first_usable >= first_lba + 1 + partition_table_blocks,
158 "Invalid first_usable {}",
159 self.first_usable
160 );
161 ensure!(
162 self.first_usable <= self.last_usable
163 && self.last_usable + partition_table_blocks < second_lba,
164 "Invalid last_usable {}",
165 self.last_usable
166 );
167
168 if first_lba == self.current_lba {
169 ensure!(self.part_start == first_lba + 1, "Invalid part_start {}", self.part_start);
170 } else {
171 ensure!(
172 self.part_start == self.last_usable + 1,
173 "Invalid part_start {}",
174 self.part_start
175 );
176 }
177
178 Ok(())
179 }
180}
181
182#[derive(Clone, Debug, Eq, PartialEq, Immutable, IntoBytes, KnownLayout, FromBytes)]
183#[repr(C)]
184pub struct PartitionTableEntry {
185 pub type_guid: [u8; 16],
186 pub instance_guid: [u8; 16],
187 pub first_lba: u64,
188 pub last_lba: u64,
189 pub flags: u64,
190 pub name: [u16; 36],
191}
192
193impl PartitionTableEntry {
194 pub fn is_empty(&self) -> bool {
195 self.as_bytes().iter().all(|b| *b == 0)
196 }
197
198 pub fn empty() -> Self {
199 Self {
200 type_guid: [0u8; 16],
201 instance_guid: [0u8; 16],
202 first_lba: 0,
203 last_lba: 0,
204 flags: 0,
205 name: [0u16; 36],
206 }
207 }
208
209 pub fn ensure_integrity(&self, first_usable: u64, last_usable: u64) -> Result<(), Error> {
210 ensure!(self.type_guid != [0u8; 16], "Empty type GUID");
211 ensure!(self.instance_guid != [0u8; 16], "Empty instance GUID");
212 ensure!(self.first_lba >= first_usable, "Invalid first LBA");
213 ensure!(
214 self.last_lba <= last_usable && self.last_lba >= self.first_lba,
215 "Invalid last LBA"
216 );
217 Ok(())
218 }
219}
220
221#[derive(Eq, thiserror::Error, Clone, Debug, PartialEq)]
222pub enum FormatError {
223 #[error("Invalid arguments")]
224 InvalidArguments,
225 #[error("No space")]
226 NoSpace,
227}
228
229pub fn serialize_partition_table(
233 header: &mut Header,
234 block_size: usize,
235 num_blocks: u64,
236 entries: &[PartitionTableEntry],
237) -> Result<Vec<u8>, FormatError> {
238 let crc_algo = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
239 let mut digest = crc_algo.digest();
240 let partition_table_len = header.part_size as usize * entries.len();
241 let partition_table_len = partition_table_len
242 .checked_next_multiple_of(block_size)
243 .ok_or(FormatError::InvalidArguments)?;
244 let partition_table_blocks = (partition_table_len / block_size) as u64;
245 let mut partition_table = vec![0u8; partition_table_len];
246 let mut partition_table_view = &mut partition_table[..];
247 let first_usable = partition_table_blocks + 2;
249 let last_usable = num_blocks.saturating_sub(partition_table_blocks + 2);
252 if first_usable > last_usable {
253 return Err(FormatError::NoSpace);
254 }
255 let mut used_ranges = vec![0..first_usable, last_usable + 1..num_blocks];
256 let part_size = header.part_size as usize;
257 for entry in entries {
258 let part_raw = entry.as_bytes();
259 assert!(part_raw.len() == part_size);
260 if !entry.is_empty() {
261 entry
262 .ensure_integrity(first_usable, last_usable)
263 .map_err(|_| FormatError::InvalidArguments)?;
264 used_ranges.push(entry.first_lba..entry.last_lba + 1);
265 partition_table_view[..part_raw.len()].copy_from_slice(part_raw);
266 }
267 digest.update(part_raw);
268 partition_table_view = &mut partition_table_view[part_size..];
269 }
270 used_ranges.sort_by_key(|range| range.start);
271 for [a, b] in used_ranges.array_windows() {
272 if a.end > b.start {
273 return Err(FormatError::InvalidArguments);
274 }
275 }
276 header.first_usable = first_usable;
277 header.last_usable = last_usable;
278 header.num_parts = entries.len() as u32;
279 header.crc32_parts = digest.finalize();
280 header.crc32 = header.compute_checksum();
281 Ok(partition_table)
282}
283
284#[cfg(test)]
285mod tests {
286 use super::{GPT_HEADER_SIZE, Header};
287
288 #[fuchsia::test]
289 fn header_crc() {
290 let nblocks = 8;
291 let partition_table_nblocks = 1;
292 let mut header = Header {
293 signature: [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54],
294 revision: 0x10000,
295 header_size: GPT_HEADER_SIZE as u32,
296 crc32: 0,
297 reserved: 0,
298 current_lba: 1,
299 backup_lba: nblocks - 1,
300 first_usable: 2 + partition_table_nblocks,
301 last_usable: nblocks - (2 + partition_table_nblocks),
302 disk_guid: [0u8; 16],
303 part_start: 2,
304 num_parts: 1,
305 part_size: 128,
306 crc32_parts: 0,
307 zerocopy_padding: 0,
308 };
309 header.crc32 = header.compute_checksum();
310
311 header.ensure_integrity(nblocks, 512).expect("Header should be valid");
312
313 header.num_parts = 2;
315
316 header.ensure_integrity(nblocks, 512).expect_err("Header should be invalid");
317 }
318
319 #[fuchsia::test]
320 fn test_backup_lba_validation() {
321 let nblocks = 10;
322 let partition_table_nblocks = 1;
323 let mut header = Header {
324 signature: [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54],
325 revision: 0x10000,
326 header_size: GPT_HEADER_SIZE as u32,
327 crc32: 0,
328 reserved: 0,
329 current_lba: 1,
330 backup_lba: nblocks - 1,
331 first_usable: 2 + partition_table_nblocks,
332 last_usable: nblocks - (2 + partition_table_nblocks),
333 disk_guid: [0u8; 16],
334 part_start: 2,
335 num_parts: 1,
336 part_size: 128,
337 crc32_parts: 0,
338 zerocopy_padding: 0,
339 };
340
341 header.crc32 = header.compute_checksum();
344 header.ensure_integrity(nblocks, 512).expect("Header should be valid");
345
346 header.backup_lba = nblocks - 2;
348 header.last_usable = 6;
349 header.crc32 = header.compute_checksum();
350 header
351 .ensure_integrity(nblocks, 512)
352 .expect("Header should be valid with relaxed backup_lba");
353
354 header.backup_lba = nblocks;
356 header.last_usable = nblocks - (2 + partition_table_nblocks);
357 header.crc32 = header.compute_checksum();
358 header.ensure_integrity(nblocks, 512).expect_err("backup_lba >= nblocks should be invalid");
359
360 header.current_lba = nblocks - 1;
362 header.backup_lba = 1;
363 header.part_start = header.last_usable + 1;
364 header.crc32 = header.compute_checksum();
365 header
366 .ensure_integrity(nblocks, 512)
367 .expect("Backup header should be valid with backup_lba == 1");
368
369 header.backup_lba = 2;
370 header.crc32 = header.compute_checksum();
371 header
372 .ensure_integrity(nblocks, 512)
373 .expect_err("Backup header should be invalid with backup_lba != 1");
374 }
375}