disk/
gpt.rs

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
7use std::convert::TryInto;
8use std::io;
9use std::io::Write;
10use std::num::TryFromIntError;
11
12use crc32fast::Hasher;
13use remain::sorted;
14use thiserror::Error as ThisError;
15use uuid::Uuid;
16
17/// The size in bytes of a disk sector (also called a block).
18pub const SECTOR_SIZE: u64 = 1 << 9;
19/// The size in bytes on an MBR partition entry.
20const MBR_PARTITION_ENTRY_SIZE: usize = 16;
21/// The size in bytes of a GPT header.
22pub 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.
25pub const GPT_NUM_PARTITIONS: u32 = 128;
26/// The size in bytes of a single GPT partition entry.
27pub 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.
30pub 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.
33pub const GPT_END_SIZE: u64 = SECTOR_SIZE * 33;
34
35#[sorted]
36#[derive(ThisError, Debug)]
37pub 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.
50pub 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)]
82struct 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
98impl GptHeader {
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).
136pub 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)]
183pub 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.
196impl Default for GptPartitionEntry {
197    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
209impl GptPartitionEntry {
210    /// Write out the partition table entry. It will take
211    /// `GPT_PARTITION_ENTRY_SIZE` bytes.
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.
230fn 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)]
241mod tests {
242    use super::*;
243
244    #[test]
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]
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]
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}