1use anyhow::{anyhow, ensure, Error};
6use crc::Hasher32 as _;
7use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
8
9const MAX_PARTITION_ENTRIES: u32 = 128;
10
11pub const GPT_SIGNATURE: [u8; 8] = [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54];
12pub const GPT_REVISION: u32 = 0x10000;
13pub const GPT_HEADER_SIZE: usize = 92;
14
15#[derive(Clone, Debug, Eq, PartialEq, Immutable, IntoBytes, KnownLayout, FromBytes)]
17#[repr(C)]
18pub struct Header {
19 pub signature: [u8; 8],
21 pub revision: u32,
23 pub header_size: u32,
25 pub crc32: u32,
27 pub reserved: u32,
29 pub current_lba: u64,
31 pub backup_lba: u64,
33 pub first_usable: u64,
35 pub last_usable: u64,
37 pub disk_guid: [u8; 16],
39 pub part_start: u64,
41 pub num_parts: u32,
43 pub part_size: u32,
45 pub crc32_parts: u32,
47 zerocopy_padding: u32,
50}
51
52impl Header {
53 pub fn new(block_count: u64, block_size: u32, num_parts: u32) -> Result<Self, Error> {
54 ensure!(block_size > 0 && block_size.is_power_of_two(), "Invalid block size");
55 let bs = block_size as u64;
56
57 let part_size = std::mem::size_of::<PartitionTableEntry>();
58 let partition_table_len = num_parts as u64 * part_size as u64;
59 let partition_table_blocks = partition_table_len.checked_next_multiple_of(bs).unwrap() / bs;
60
61 ensure!(block_count > 1 + 2 * (1 + partition_table_blocks), "Too few blocks");
64
65 let mut this = Self {
66 signature: GPT_SIGNATURE,
67 revision: GPT_REVISION,
68 header_size: GPT_HEADER_SIZE as u32,
69 crc32: 0,
70 reserved: 0,
71 current_lba: 1,
72 backup_lba: block_count - 1,
73 first_usable: 2 + partition_table_blocks,
74 last_usable: block_count - (2 + partition_table_blocks),
75 disk_guid: uuid::Uuid::new_v4().into_bytes(),
76 part_start: 2,
77 num_parts,
78 part_size: part_size as u32,
79 crc32_parts: 0,
80 zerocopy_padding: 0,
81 };
82 this.update_checksum();
83 Ok(this)
84 }
85
86 pub fn compute_checksum(&self) -> u32 {
88 let mut header_copy = self.clone();
89 header_copy.crc32 = 0;
90 crc::crc32::checksum_ieee(&header_copy.as_bytes()[..GPT_HEADER_SIZE])
91 }
92
93 fn update_checksum(&mut self) {
94 self.crc32 = 0;
95 let crc = crc::crc32::checksum_ieee(&self.as_bytes()[..GPT_HEADER_SIZE]);
96 self.crc32 = crc;
97 }
98
99 pub fn ensure_integrity(&self, block_count: u64, block_size: u64) -> Result<(), Error> {
101 ensure!(self.signature == GPT_SIGNATURE, "Bad signature {:x?}", self.signature);
102 ensure!(self.revision == GPT_REVISION, "Bad revision {:x}", self.revision);
103 ensure!(
104 self.header_size as usize == GPT_HEADER_SIZE,
105 "Bad header size {}",
106 self.header_size
107 );
108
109 ensure!(self.crc32 == self.compute_checksum(), "Invalid header checksum");
112
113 ensure!(self.num_parts <= MAX_PARTITION_ENTRIES, "Invalid num_parts {}", self.num_parts);
114 ensure!(
115 self.part_size as usize == std::mem::size_of::<PartitionTableEntry>(),
116 "Invalid part_size {}",
117 self.part_size
118 );
119 let partition_table_blocks = (self
120 .num_parts
121 .checked_mul(self.part_size)
122 .and_then(|v| v.checked_next_multiple_of(block_size as u32))
123 .ok_or_else(|| anyhow!("Partition table size overflow"))?
124 as u64)
125 / block_size;
126 ensure!(partition_table_blocks < block_count, "Invalid partition table size");
127
128 ensure!(
132 self.current_lba == 1 || self.current_lba == block_count - 1,
133 "Invalid current_lba {}",
134 self.current_lba
135 );
136 let (first_lba, second_lba) = if self.current_lba == 1 {
137 (self.current_lba, self.backup_lba)
138 } else {
139 (self.backup_lba, self.current_lba)
140 };
141
142 ensure!(
143 self.first_usable >= first_lba + partition_table_blocks,
144 "Invalid first_usable {}",
145 self.first_usable
146 );
147 ensure!(
148 self.first_usable <= self.last_usable
149 && self.last_usable + partition_table_blocks <= second_lba,
150 "Invalid last_usable {}",
151 self.last_usable
152 );
153
154 if first_lba == self.current_lba {
155 ensure!(self.part_start == first_lba + 1, "Invalid part_start {}", self.part_start);
156 } else {
157 ensure!(
158 self.part_start == self.last_usable + 1,
159 "Invalid part_start {}",
160 self.part_start
161 );
162 }
163
164 Ok(())
165 }
166}
167
168#[derive(Clone, Debug, Eq, PartialEq, Immutable, IntoBytes, KnownLayout, FromBytes)]
169#[repr(C)]
170pub struct PartitionTableEntry {
171 pub type_guid: [u8; 16],
172 pub instance_guid: [u8; 16],
173 pub first_lba: u64,
174 pub last_lba: u64,
175 pub flags: u64,
176 pub name: [u16; 36],
177}
178
179impl PartitionTableEntry {
180 pub fn is_empty(&self) -> bool {
181 self.as_bytes().iter().all(|b| *b == 0)
182 }
183
184 pub fn empty() -> Self {
185 Self {
186 type_guid: [0u8; 16],
187 instance_guid: [0u8; 16],
188 first_lba: 0,
189 last_lba: 0,
190 flags: 0,
191 name: [0u16; 36],
192 }
193 }
194
195 pub fn ensure_integrity(&self) -> Result<(), Error> {
196 ensure!(self.type_guid != [0u8; 16], "Empty type GUID");
197 ensure!(self.instance_guid != [0u8; 16], "Empty instance GUID");
198 ensure!(self.first_lba != 0, "Invalid first LBA");
199 ensure!(self.last_lba != 0 && self.last_lba >= self.first_lba, "Invalid last LBA");
200 Ok(())
201 }
202}
203
204#[derive(Eq, thiserror::Error, Clone, Debug, PartialEq)]
205pub enum FormatError {
206 #[error("Invalid arguments")]
207 InvalidArguments,
208 #[error("No space")]
209 NoSpace,
210}
211
212pub fn serialize_partition_table(
216 header: &mut Header,
217 block_size: usize,
218 num_blocks: u64,
219 entries: &[PartitionTableEntry],
220) -> Result<Vec<u8>, FormatError> {
221 let mut digest = crc::crc32::Digest::new(crc::crc32::IEEE);
222 let partition_table_len = header.part_size as usize * entries.len();
223 let partition_table_len = partition_table_len
224 .checked_next_multiple_of(block_size)
225 .ok_or(FormatError::InvalidArguments)?;
226 let partition_table_blocks = (partition_table_len / block_size) as u64;
227 let mut partition_table = vec![0u8; partition_table_len];
228 let mut partition_table_view = &mut partition_table[..];
229 let first_usable = partition_table_blocks + 2;
231 let last_usable = num_blocks.saturating_sub(partition_table_blocks + 2);
234 if first_usable > last_usable {
235 return Err(FormatError::NoSpace);
236 }
237 let mut used_ranges = vec![0..first_usable, last_usable + 1..num_blocks];
238 let part_size = header.part_size as usize;
239 for entry in entries {
240 let part_raw = entry.as_bytes();
241 assert!(part_raw.len() == part_size);
242 if !entry.is_empty() {
243 entry.ensure_integrity().map_err(|_| FormatError::InvalidArguments)?;
244 used_ranges.push(entry.first_lba..entry.last_lba + 1);
245 partition_table_view[..part_raw.len()].copy_from_slice(part_raw);
246 }
247 digest.write(part_raw);
248 partition_table_view = &mut partition_table_view[part_size..];
249 }
250 used_ranges.sort_by_key(|range| range.start);
251 for ranges in used_ranges.windows(2) {
252 if ranges[0].end > ranges[1].start {
253 return Err(FormatError::InvalidArguments);
254 }
255 }
256 header.first_usable = first_usable;
257 header.last_usable = last_usable;
258 header.num_parts = entries.len() as u32;
259 header.crc32_parts = digest.sum32();
260 header.crc32 = header.compute_checksum();
261 Ok(partition_table)
262}
263
264#[cfg(test)]
265mod tests {
266 use super::{Header, GPT_HEADER_SIZE};
267
268 #[fuchsia::test]
269 fn header_crc() {
270 let nblocks = 8;
271 let partition_table_nblocks = 1;
272 let mut header = Header {
273 signature: [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54],
274 revision: 0x10000,
275 header_size: GPT_HEADER_SIZE as u32,
276 crc32: 0,
277 reserved: 0,
278 current_lba: 1,
279 backup_lba: nblocks - 1,
280 first_usable: 2 + partition_table_nblocks,
281 last_usable: nblocks - (2 + partition_table_nblocks),
282 disk_guid: [0u8; 16],
283 part_start: 2,
284 num_parts: 1,
285 part_size: 128,
286 crc32_parts: 0,
287 zerocopy_padding: 0,
288 };
289 header.crc32 = header.compute_checksum();
290
291 header.ensure_integrity(nblocks, 512).expect("Header should be valid");
292
293 header.num_parts = 2;
295
296 header.ensure_integrity(nblocks, 512).expect_err("Header should be invalid");
297 }
298}