xref: /aosp_15_r20/external/crosvm/disk/src/gpt.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2021 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Functions for writing GUID Partition Tables for use in a composite disk image.
6 
7 use std::convert::TryInto;
8 use std::io;
9 use std::io::Write;
10 use std::num::TryFromIntError;
11 
12 use crc32fast::Hasher;
13 use remain::sorted;
14 use thiserror::Error as ThisError;
15 use uuid::Uuid;
16 
17 /// The size in bytes of a disk sector (also called a block).
18 pub const SECTOR_SIZE: u64 = 1 << 9;
19 /// The size in bytes on an MBR partition entry.
20 const MBR_PARTITION_ENTRY_SIZE: usize = 16;
21 /// The size in bytes of a GPT header.
22 pub const GPT_HEADER_SIZE: u32 = 92;
23 /// The number of partition entries in the GPT, which is the maximum number of partitions which are
24 /// supported.
25 pub const GPT_NUM_PARTITIONS: u32 = 128;
26 /// The size in bytes of a single GPT partition entry.
27 pub const GPT_PARTITION_ENTRY_SIZE: u32 = 128;
28 /// The size in bytes of everything before the first partition: i.e. the MBR, GPT header and GPT
29 /// partition entries.
30 pub const GPT_BEGINNING_SIZE: u64 = SECTOR_SIZE * 40;
31 /// The size in bytes of everything after the last partition: i.e. the GPT partition entries and GPT
32 /// footer.
33 pub const GPT_END_SIZE: u64 = SECTOR_SIZE * 33;
34 
35 #[sorted]
36 #[derive(ThisError, Debug)]
37 pub enum Error {
38     /// The disk size was invalid (too large).
39     #[error("invalid disk size: {0}")]
40     InvalidDiskSize(TryFromIntError),
41     /// There was an error writing data to one of the image files.
42     #[error("failed to write data: {0}")]
43     WritingData(io::Error),
44 }
45 
46 /// Write a protective MBR for a disk of the given total size (in bytes).
47 ///
48 /// This should be written at the start of the disk, before the GPT header. It is one `SECTOR_SIZE`
49 /// long.
write_protective_mbr(file: &mut impl Write, disk_size: u64) -> Result<(), Error>50 pub fn write_protective_mbr(file: &mut impl Write, disk_size: u64) -> Result<(), Error> {
51     // Bootstrap code
52     file.write_all(&[0; 446]).map_err(Error::WritingData)?;
53 
54     // Partition status
55     file.write_all(&[0x00]).map_err(Error::WritingData)?;
56     // Begin CHS
57     file.write_all(&[0; 3]).map_err(Error::WritingData)?;
58     // Partition type
59     file.write_all(&[0xEE]).map_err(Error::WritingData)?;
60     // End CHS
61     file.write_all(&[0; 3]).map_err(Error::WritingData)?;
62     let first_lba: u32 = 1;
63     file.write_all(&first_lba.to_le_bytes())
64         .map_err(Error::WritingData)?;
65     let number_of_sectors: u32 = (disk_size / SECTOR_SIZE)
66         .try_into()
67         .map_err(Error::InvalidDiskSize)?;
68     file.write_all(&number_of_sectors.to_le_bytes())
69         .map_err(Error::WritingData)?;
70 
71     // Three more empty partitions
72     file.write_all(&[0; MBR_PARTITION_ENTRY_SIZE * 3])
73         .map_err(Error::WritingData)?;
74 
75     // Boot signature
76     file.write_all(&[0x55, 0xAA]).map_err(Error::WritingData)?;
77 
78     Ok(())
79 }
80 
81 #[derive(Clone, Debug, Default, Eq, PartialEq)]
82 struct GptHeader {
83     signature: [u8; 8],
84     revision: [u8; 4],
85     header_size: u32,
86     header_crc32: u32,
87     current_lba: u64,
88     backup_lba: u64,
89     first_usable_lba: u64,
90     last_usable_lba: u64,
91     disk_guid: Uuid,
92     partition_entries_lba: u64,
93     num_partition_entries: u32,
94     partition_entry_size: u32,
95     partition_entries_crc32: u32,
96 }
97 
98 impl GptHeader {
write_bytes(&self, out: &mut impl Write) -> Result<(), Error>99     fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> {
100         out.write_all(&self.signature).map_err(Error::WritingData)?;
101         out.write_all(&self.revision).map_err(Error::WritingData)?;
102         out.write_all(&self.header_size.to_le_bytes())
103             .map_err(Error::WritingData)?;
104         out.write_all(&self.header_crc32.to_le_bytes())
105             .map_err(Error::WritingData)?;
106         // Reserved
107         out.write_all(&[0; 4]).map_err(Error::WritingData)?;
108         out.write_all(&self.current_lba.to_le_bytes())
109             .map_err(Error::WritingData)?;
110         out.write_all(&self.backup_lba.to_le_bytes())
111             .map_err(Error::WritingData)?;
112         out.write_all(&self.first_usable_lba.to_le_bytes())
113             .map_err(Error::WritingData)?;
114         out.write_all(&self.last_usable_lba.to_le_bytes())
115             .map_err(Error::WritingData)?;
116 
117         // GUID is mixed-endian for some reason, so we can't just use `Uuid::as_bytes()`.
118         write_guid(out, self.disk_guid).map_err(Error::WritingData)?;
119 
120         out.write_all(&self.partition_entries_lba.to_le_bytes())
121             .map_err(Error::WritingData)?;
122         out.write_all(&self.num_partition_entries.to_le_bytes())
123             .map_err(Error::WritingData)?;
124         out.write_all(&self.partition_entry_size.to_le_bytes())
125             .map_err(Error::WritingData)?;
126         out.write_all(&self.partition_entries_crc32.to_le_bytes())
127             .map_err(Error::WritingData)?;
128         Ok(())
129     }
130 }
131 
132 /// Write a GPT header for the disk.
133 ///
134 /// It may either be a primary header (which should go at LBA 1) or a secondary header (which should
135 /// go at the end of the disk).
write_gpt_header( out: &mut impl Write, disk_guid: Uuid, partition_entries_crc32: u32, secondary_table_offset: u64, secondary: bool, ) -> Result<(), Error>136 pub fn write_gpt_header(
137     out: &mut impl Write,
138     disk_guid: Uuid,
139     partition_entries_crc32: u32,
140     secondary_table_offset: u64,
141     secondary: bool,
142 ) -> Result<(), Error> {
143     let primary_header_lba = 1;
144     let secondary_header_lba = (secondary_table_offset + GPT_END_SIZE) / SECTOR_SIZE - 1;
145     let mut gpt_header = GptHeader {
146         signature: *b"EFI PART",
147         revision: [0, 0, 1, 0],
148         header_size: GPT_HEADER_SIZE,
149         current_lba: if secondary {
150             secondary_header_lba
151         } else {
152             primary_header_lba
153         },
154         backup_lba: if secondary {
155             primary_header_lba
156         } else {
157             secondary_header_lba
158         },
159         first_usable_lba: GPT_BEGINNING_SIZE / SECTOR_SIZE,
160         last_usable_lba: secondary_table_offset / SECTOR_SIZE - 1,
161         disk_guid,
162         partition_entries_lba: 2,
163         num_partition_entries: GPT_NUM_PARTITIONS,
164         partition_entry_size: GPT_PARTITION_ENTRY_SIZE,
165         partition_entries_crc32,
166         header_crc32: 0,
167     };
168 
169     // Write once to a temporary buffer to calculate the CRC.
170     let mut header_without_crc = [0u8; GPT_HEADER_SIZE as usize];
171     gpt_header.write_bytes(&mut &mut header_without_crc[..])?;
172     let mut hasher = Hasher::new();
173     hasher.update(&header_without_crc);
174     gpt_header.header_crc32 = hasher.finalize();
175 
176     gpt_header.write_bytes(out)?;
177 
178     Ok(())
179 }
180 
181 /// A GPT entry for a particular partition.
182 #[derive(Clone, Debug, Eq, PartialEq)]
183 pub struct GptPartitionEntry {
184     pub partition_type_guid: Uuid,
185     pub unique_partition_guid: Uuid,
186     pub first_lba: u64,
187     pub last_lba: u64,
188     pub attributes: u64,
189     /// UTF-16LE
190     pub partition_name: [u16; 36],
191 }
192 
193 // This is implemented manually because `Default` isn't implemented in the standard library for
194 // arrays of more than 32 elements. If that gets implemented (now than const generics are in) then
195 // we can derive this instead.
196 impl Default for GptPartitionEntry {
default() -> Self197     fn default() -> Self {
198         Self {
199             partition_type_guid: Default::default(),
200             unique_partition_guid: Default::default(),
201             first_lba: 0,
202             last_lba: 0,
203             attributes: 0,
204             partition_name: [0; 36],
205         }
206     }
207 }
208 
209 impl GptPartitionEntry {
210     /// Write out the partition table entry. It will take
211     /// `GPT_PARTITION_ENTRY_SIZE` bytes.
write_bytes(&self, out: &mut impl Write) -> Result<(), Error>212     pub fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> {
213         write_guid(out, self.partition_type_guid).map_err(Error::WritingData)?;
214         write_guid(out, self.unique_partition_guid).map_err(Error::WritingData)?;
215         out.write_all(&self.first_lba.to_le_bytes())
216             .map_err(Error::WritingData)?;
217         out.write_all(&self.last_lba.to_le_bytes())
218             .map_err(Error::WritingData)?;
219         out.write_all(&self.attributes.to_le_bytes())
220             .map_err(Error::WritingData)?;
221         for code_unit in &self.partition_name {
222             out.write_all(&code_unit.to_le_bytes())
223                 .map_err(Error::WritingData)?;
224         }
225         Ok(())
226     }
227 }
228 
229 /// Write a UUID in the mixed-endian format which GPT uses for GUIDs.
write_guid(out: &mut impl Write, guid: Uuid) -> Result<(), io::Error>230 fn write_guid(out: &mut impl Write, guid: Uuid) -> Result<(), io::Error> {
231     let guid_fields = guid.as_fields();
232     out.write_all(&guid_fields.0.to_le_bytes())?;
233     out.write_all(&guid_fields.1.to_le_bytes())?;
234     out.write_all(&guid_fields.2.to_le_bytes())?;
235     out.write_all(guid_fields.3)?;
236 
237     Ok(())
238 }
239 
240 #[cfg(test)]
241 mod tests {
242     use super::*;
243 
244     #[test]
protective_mbr_size()245     fn protective_mbr_size() {
246         let mut buffer = vec![];
247         write_protective_mbr(&mut buffer, 1000 * SECTOR_SIZE).unwrap();
248 
249         assert_eq!(buffer.len(), SECTOR_SIZE as usize);
250     }
251 
252     #[test]
header_size()253     fn header_size() {
254         let mut buffer = vec![];
255         write_gpt_header(
256             &mut buffer,
257             Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
258             42,
259             1000 * SECTOR_SIZE,
260             false,
261         )
262         .unwrap();
263 
264         assert_eq!(buffer.len(), GPT_HEADER_SIZE as usize);
265     }
266 
267     #[test]
partition_entry_size()268     fn partition_entry_size() {
269         let mut buffer = vec![];
270         GptPartitionEntry::default()
271             .write_bytes(&mut buffer)
272             .unwrap();
273 
274         assert_eq!(buffer.len(), GPT_PARTITION_ENTRY_SIZE as usize);
275     }
276 }
277