blob: 346a40a170e5c64451a7e6a4e5a43175f364d74d [file] [log] [blame]
Andrew Walbran3eca16c2021-06-14 11:15:14 +00001// Copyright 2021, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Functions for writing GUID Partition Tables for use in a composite disk image.
16
17use anyhow::Error;
18use crc32fast::Hasher;
19use std::convert::TryInto;
20use std::io::Write;
21use uuid::Uuid;
22
23/// The size in bytes of a disk sector (also called a block).
24pub const SECTOR_SIZE: u64 = 1 << 9;
25/// The size in bytes on an MBR partition entry.
26const MBR_PARTITION_ENTRY_SIZE: usize = 16;
27/// The size in bytes of a GPT header.
28pub const GPT_HEADER_SIZE: u32 = 92;
29/// The number of partition entries in the GPT, which is the maximum number of partitions which are
30/// supported.
31pub const GPT_NUM_PARTITIONS: u32 = 128;
32/// The size in bytes of a single GPT partition entry.
33pub const GPT_PARTITION_ENTRY_SIZE: u32 = 128;
34/// The size in bytes of everything before the first partition: i.e. the MBR, GPT header and GPT
35/// partition entries.
36pub const GPT_BEGINNING_SIZE: u64 = SECTOR_SIZE * 40;
37/// The size in bytes of everything after the last partition: i.e. the GPT partition entries and GPT
38/// footer.
39pub const GPT_END_SIZE: u64 = SECTOR_SIZE * 33;
40
41/// Write a protective MBR for a disk of the given total size (in bytes).
42///
43/// This should be written at the start of the disk, before the GPT header. It
44/// is one `SECTOR_SIZE` long.
45pub fn write_protective_mbr(file: &mut impl Write, disk_size: u64) -> Result<(), Error> {
46 // Bootstrap code
47 file.write_all(&[0; 446])?;
48
49 // Partition status
50 file.write_all(&[0x00])?;
51 // Begin CHS
52 file.write_all(&[0; 3])?;
53 // Partition type
54 file.write_all(&[0xEE])?;
55 // End CHS
56 file.write_all(&[0; 3])?;
57 let first_lba: u32 = 1;
58 file.write_all(&first_lba.to_le_bytes())?;
59 let number_of_sectors: u32 = (disk_size / SECTOR_SIZE).try_into()?;
60 file.write_all(&number_of_sectors.to_le_bytes())?;
61
62 // Three more empty partitions
63 file.write_all(&[0; MBR_PARTITION_ENTRY_SIZE * 3])?;
64
65 // Boot signature
66 file.write_all(&[0x55, 0xAA])?;
67
68 Ok(())
69}
70
71#[derive(Clone, Debug, Default, Eq, PartialEq)]
72struct GptHeader {
73 signature: [u8; 8],
74 revision: [u8; 4],
75 header_size: u32,
76 header_crc32: u32,
77 current_lba: u64,
78 backup_lba: u64,
79 first_usable_lba: u64,
80 last_usable_lba: u64,
81 disk_guid: Uuid,
82 partition_entries_lba: u64,
83 num_partition_entries: u32,
84 partition_entry_size: u32,
85 partition_entries_crc32: u32,
86}
87
88impl GptHeader {
89 fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> {
90 out.write_all(&self.signature)?;
91 out.write_all(&self.revision)?;
92 out.write_all(&self.header_size.to_le_bytes())?;
93 out.write_all(&self.header_crc32.to_le_bytes())?;
94 // Reserved
95 out.write_all(&[0; 4])?;
96 out.write_all(&self.current_lba.to_le_bytes())?;
97 out.write_all(&self.backup_lba.to_le_bytes())?;
98 out.write_all(&self.first_usable_lba.to_le_bytes())?;
99 out.write_all(&self.last_usable_lba.to_le_bytes())?;
100
101 // GUID is mixed-endian for some reason, so we can't just use `Uuid::as_bytes()`.
102 write_guid(out, self.disk_guid)?;
103
104 out.write_all(&self.partition_entries_lba.to_le_bytes())?;
105 out.write_all(&self.num_partition_entries.to_le_bytes())?;
106 out.write_all(&self.partition_entry_size.to_le_bytes())?;
107 out.write_all(&self.partition_entries_crc32.to_le_bytes())?;
108 Ok(())
109 }
110}
111
112/// Write a GPT header for the disk.
113///
114/// It may either be a primary header (which should go at LBA 1) or a secondary header (which should
115/// go at the end of the disk).
116pub fn write_gpt_header(
117 out: &mut impl Write,
118 disk_guid: Uuid,
119 partition_entries_crc32: u32,
120 secondary_table_offset: u64,
121 secondary: bool,
122) -> Result<(), Error> {
123 let primary_header_lba = 1;
124 let secondary_header_lba = (secondary_table_offset + GPT_END_SIZE) / SECTOR_SIZE - 1;
125 let mut gpt_header = GptHeader {
126 signature: *b"EFI PART",
127 revision: [0, 0, 1, 0],
128 header_size: GPT_HEADER_SIZE,
129 current_lba: if secondary { secondary_header_lba } else { primary_header_lba },
130 backup_lba: if secondary { primary_header_lba } else { secondary_header_lba },
131 first_usable_lba: GPT_BEGINNING_SIZE / SECTOR_SIZE,
132 last_usable_lba: secondary_table_offset / SECTOR_SIZE - 1,
133 disk_guid,
134 partition_entries_lba: 2,
135 num_partition_entries: GPT_NUM_PARTITIONS,
136 partition_entry_size: GPT_PARTITION_ENTRY_SIZE,
137 partition_entries_crc32,
138 header_crc32: 0,
139 };
140
141 // Write once to a temporary buffer to calculate the CRC.
142 let mut header_without_crc = [0u8; GPT_HEADER_SIZE as usize];
143 gpt_header.write_bytes(&mut &mut header_without_crc[..])?;
144 let mut hasher = Hasher::new();
145 hasher.update(&header_without_crc);
146 gpt_header.header_crc32 = hasher.finalize();
147
148 gpt_header.write_bytes(out)?;
149
150 Ok(())
151}
152
153/// A GPT entry for a particular partition.
154#[derive(Clone, Debug, Eq, PartialEq)]
155pub struct GptPartitionEntry {
156 pub partition_type_guid: Uuid,
157 pub unique_partition_guid: Uuid,
158 pub first_lba: u64,
159 pub last_lba: u64,
160 pub attributes: u64,
161 /// UTF-16LE
162 pub partition_name: [u16; 36],
163}
164
165// TODO: Derive this once arrays of more than 32 elements have default values.
166impl Default for GptPartitionEntry {
167 fn default() -> Self {
168 Self {
169 partition_type_guid: Default::default(),
170 unique_partition_guid: Default::default(),
171 first_lba: 0,
172 last_lba: 0,
173 attributes: 0,
174 partition_name: [0; 36],
175 }
176 }
177}
178
179impl GptPartitionEntry {
180 /// Write out the partition table entry. It will take
181 /// `GPT_PARTITION_ENTRY_SIZE` bytes.
182 pub fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> {
183 write_guid(out, self.partition_type_guid)?;
184 write_guid(out, self.unique_partition_guid)?;
185 out.write_all(&self.first_lba.to_le_bytes())?;
186 out.write_all(&self.last_lba.to_le_bytes())?;
187 out.write_all(&self.attributes.to_le_bytes())?;
188 for code_unit in &self.partition_name {
189 out.write_all(&code_unit.to_le_bytes())?;
190 }
191 Ok(())
192 }
193}
194
195/// Write a UUID in the mixed-endian format which GPT uses for GUIDs.
196fn write_guid(out: &mut impl Write, guid: Uuid) -> Result<(), Error> {
197 let guid_fields = guid.as_fields();
198 out.write_all(&guid_fields.0.to_le_bytes())?;
199 out.write_all(&guid_fields.1.to_le_bytes())?;
200 out.write_all(&guid_fields.2.to_le_bytes())?;
201 out.write_all(guid_fields.3)?;
202
203 Ok(())
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn protective_mbr_size() {
212 let mut buffer = vec![];
213 write_protective_mbr(&mut buffer, 1000 * SECTOR_SIZE).unwrap();
214
215 assert_eq!(buffer.len(), SECTOR_SIZE as usize);
216 }
217
218 #[test]
219 fn header_size() {
220 let mut buffer = vec![];
221 write_gpt_header(
222 &mut buffer,
223 Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
224 42,
225 1000 * SECTOR_SIZE,
226 false,
227 )
228 .unwrap();
229
230 assert_eq!(buffer.len(), GPT_HEADER_SIZE as usize);
231 }
232
233 #[test]
234 fn partition_entry_size() {
235 let mut buffer = vec![];
236 GptPartitionEntry::default().write_bytes(&mut buffer).unwrap();
237
238 assert_eq!(buffer.len(), GPT_PARTITION_ENTRY_SIZE as usize);
239 }
240}