// Copyright 2021 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //! Functions for writing GUID Partition Tables for use in a composite disk image. use std::convert::TryInto; use std::io::{self, Write}; use std::num::TryFromIntError; use crc32fast::Hasher; use remain::sorted; use thiserror::Error as ThisError; use uuid::Uuid; /// The size in bytes of a disk sector (also called a block). pub const SECTOR_SIZE: u64 = 1 << 9; /// The size in bytes on an MBR partition entry. const MBR_PARTITION_ENTRY_SIZE: usize = 16; /// The size in bytes of a GPT header. pub const GPT_HEADER_SIZE: u32 = 92; /// The number of partition entries in the GPT, which is the maximum number of partitions which are /// supported. pub const GPT_NUM_PARTITIONS: u32 = 128; /// The size in bytes of a single GPT partition entry. pub const GPT_PARTITION_ENTRY_SIZE: u32 = 128; /// The size in bytes of everything before the first partition: i.e. the MBR, GPT header and GPT /// partition entries. pub const GPT_BEGINNING_SIZE: u64 = SECTOR_SIZE * 40; /// The size in bytes of everything after the last partition: i.e. the GPT partition entries and GPT /// footer. pub const GPT_END_SIZE: u64 = SECTOR_SIZE * 33; #[sorted] #[derive(ThisError, Debug)] pub enum Error { /// The disk size was invalid (too large). #[error("invalid disk size: {0}")] InvalidDiskSize(TryFromIntError), /// There was an error writing data to one of the image files. #[error("failed to write data: {0}")] WritingData(io::Error), } /// Write a protective MBR for a disk of the given total size (in bytes). /// /// This should be written at the start of the disk, before the GPT header. It is one `SECTOR_SIZE` /// long. pub fn write_protective_mbr(file: &mut impl Write, disk_size: u64) -> Result<(), Error> { // Bootstrap code file.write_all(&[0; 446]).map_err(Error::WritingData)?; // Partition status file.write_all(&[0x00]).map_err(Error::WritingData)?; // Begin CHS file.write_all(&[0; 3]).map_err(Error::WritingData)?; // Partition type file.write_all(&[0xEE]).map_err(Error::WritingData)?; // End CHS file.write_all(&[0; 3]).map_err(Error::WritingData)?; let first_lba: u32 = 1; file.write_all(&first_lba.to_le_bytes()) .map_err(Error::WritingData)?; let number_of_sectors: u32 = (disk_size / SECTOR_SIZE) .try_into() .map_err(Error::InvalidDiskSize)?; file.write_all(&number_of_sectors.to_le_bytes()) .map_err(Error::WritingData)?; // Three more empty partitions file.write_all(&[0; MBR_PARTITION_ENTRY_SIZE * 3]) .map_err(Error::WritingData)?; // Boot signature file.write_all(&[0x55, 0xAA]).map_err(Error::WritingData)?; Ok(()) } #[derive(Clone, Debug, Default, Eq, PartialEq)] struct GptHeader { signature: [u8; 8], revision: [u8; 4], header_size: u32, header_crc32: u32, current_lba: u64, backup_lba: u64, first_usable_lba: u64, last_usable_lba: u64, disk_guid: Uuid, partition_entries_lba: u64, num_partition_entries: u32, partition_entry_size: u32, partition_entries_crc32: u32, } impl GptHeader { fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> { out.write_all(&self.signature).map_err(Error::WritingData)?; out.write_all(&self.revision).map_err(Error::WritingData)?; out.write_all(&self.header_size.to_le_bytes()) .map_err(Error::WritingData)?; out.write_all(&self.header_crc32.to_le_bytes()) .map_err(Error::WritingData)?; // Reserved out.write_all(&[0; 4]).map_err(Error::WritingData)?; out.write_all(&self.current_lba.to_le_bytes()) .map_err(Error::WritingData)?; out.write_all(&self.backup_lba.to_le_bytes()) .map_err(Error::WritingData)?; out.write_all(&self.first_usable_lba.to_le_bytes()) .map_err(Error::WritingData)?; out.write_all(&self.last_usable_lba.to_le_bytes()) .map_err(Error::WritingData)?; // GUID is mixed-endian for some reason, so we can't just use `Uuid::as_bytes()`. write_guid(out, self.disk_guid).map_err(Error::WritingData)?; out.write_all(&self.partition_entries_lba.to_le_bytes()) .map_err(Error::WritingData)?; out.write_all(&self.num_partition_entries.to_le_bytes()) .map_err(Error::WritingData)?; out.write_all(&self.partition_entry_size.to_le_bytes()) .map_err(Error::WritingData)?; out.write_all(&self.partition_entries_crc32.to_le_bytes()) .map_err(Error::WritingData)?; Ok(()) } } /// Write a GPT header for the disk. /// /// It may either be a primary header (which should go at LBA 1) or a secondary header (which should /// go at the end of the disk). pub fn write_gpt_header( out: &mut impl Write, disk_guid: Uuid, partition_entries_crc32: u32, secondary_table_offset: u64, secondary: bool, ) -> Result<(), Error> { let primary_header_lba = 1; let secondary_header_lba = (secondary_table_offset + GPT_END_SIZE) / SECTOR_SIZE - 1; let mut gpt_header = GptHeader { signature: *b"EFI PART", revision: [0, 0, 1, 0], header_size: GPT_HEADER_SIZE, current_lba: if secondary { secondary_header_lba } else { primary_header_lba }, backup_lba: if secondary { primary_header_lba } else { secondary_header_lba }, first_usable_lba: GPT_BEGINNING_SIZE / SECTOR_SIZE, last_usable_lba: secondary_table_offset / SECTOR_SIZE - 1, disk_guid, partition_entries_lba: 2, num_partition_entries: GPT_NUM_PARTITIONS, partition_entry_size: GPT_PARTITION_ENTRY_SIZE, partition_entries_crc32, header_crc32: 0, }; // Write once to a temporary buffer to calculate the CRC. let mut header_without_crc = [0u8; GPT_HEADER_SIZE as usize]; gpt_header.write_bytes(&mut &mut header_without_crc[..])?; let mut hasher = Hasher::new(); hasher.update(&header_without_crc); gpt_header.header_crc32 = hasher.finalize(); gpt_header.write_bytes(out)?; Ok(()) } /// A GPT entry for a particular partition. #[derive(Clone, Debug, Eq, PartialEq)] pub struct GptPartitionEntry { pub partition_type_guid: Uuid, pub unique_partition_guid: Uuid, pub first_lba: u64, pub last_lba: u64, pub attributes: u64, /// UTF-16LE pub partition_name: [u16; 36], } // This is implemented manually because `Default` isn't implemented in the standard library for // arrays of more than 32 elements. If that gets implemented (now than const generics are in) then // we can derive this instead. impl Default for GptPartitionEntry { fn default() -> Self { Self { partition_type_guid: Default::default(), unique_partition_guid: Default::default(), first_lba: 0, last_lba: 0, attributes: 0, partition_name: [0; 36], } } } impl GptPartitionEntry { /// Write out the partition table entry. It will take /// `GPT_PARTITION_ENTRY_SIZE` bytes. pub fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> { write_guid(out, self.partition_type_guid).map_err(Error::WritingData)?; write_guid(out, self.unique_partition_guid).map_err(Error::WritingData)?; out.write_all(&self.first_lba.to_le_bytes()) .map_err(Error::WritingData)?; out.write_all(&self.last_lba.to_le_bytes()) .map_err(Error::WritingData)?; out.write_all(&self.attributes.to_le_bytes()) .map_err(Error::WritingData)?; for code_unit in &self.partition_name { out.write_all(&code_unit.to_le_bytes()) .map_err(Error::WritingData)?; } Ok(()) } } /// Write a UUID in the mixed-endian format which GPT uses for GUIDs. fn write_guid(out: &mut impl Write, guid: Uuid) -> Result<(), io::Error> { let guid_fields = guid.as_fields(); out.write_all(&guid_fields.0.to_le_bytes())?; out.write_all(&guid_fields.1.to_le_bytes())?; out.write_all(&guid_fields.2.to_le_bytes())?; out.write_all(guid_fields.3)?; Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn protective_mbr_size() { let mut buffer = vec![]; write_protective_mbr(&mut buffer, 1000 * SECTOR_SIZE).unwrap(); assert_eq!(buffer.len(), SECTOR_SIZE as usize); } #[test] fn header_size() { let mut buffer = vec![]; write_gpt_header( &mut buffer, Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd), 42, 1000 * SECTOR_SIZE, false, ) .unwrap(); assert_eq!(buffer.len(), GPT_HEADER_SIZE as usize); } #[test] fn partition_entry_size() { let mut buffer = vec![]; GptPartitionEntry::default() .write_bytes(&mut buffer) .unwrap(); assert_eq!(buffer.len(), GPT_PARTITION_ENTRY_SIZE as usize); } }