| // Copyright 2021, The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! Functions for writing GUID Partition Tables for use in a composite disk image. |
| |
| use anyhow::Error; |
| use crc32fast::Hasher; |
| use std::convert::TryInto; |
| use std::io::Write; |
| 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; |
| |
| /// 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])?; |
| |
| // Partition status |
| file.write_all(&[0x00])?; |
| // Begin CHS |
| file.write_all(&[0; 3])?; |
| // Partition type |
| file.write_all(&[0xEE])?; |
| // End CHS |
| file.write_all(&[0; 3])?; |
| let first_lba: u32 = 1; |
| file.write_all(&first_lba.to_le_bytes())?; |
| let number_of_sectors: u32 = (disk_size / SECTOR_SIZE).try_into()?; |
| file.write_all(&number_of_sectors.to_le_bytes())?; |
| |
| // Three more empty partitions |
| file.write_all(&[0; MBR_PARTITION_ENTRY_SIZE * 3])?; |
| |
| // Boot signature |
| file.write_all(&[0x55, 0xAA])?; |
| |
| 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)?; |
| out.write_all(&self.revision)?; |
| out.write_all(&self.header_size.to_le_bytes())?; |
| out.write_all(&self.header_crc32.to_le_bytes())?; |
| // Reserved |
| out.write_all(&[0; 4])?; |
| out.write_all(&self.current_lba.to_le_bytes())?; |
| out.write_all(&self.backup_lba.to_le_bytes())?; |
| out.write_all(&self.first_usable_lba.to_le_bytes())?; |
| out.write_all(&self.last_usable_lba.to_le_bytes())?; |
| |
| // GUID is mixed-endian for some reason, so we can't just use `Uuid::as_bytes()`. |
| write_guid(out, self.disk_guid)?; |
| |
| out.write_all(&self.partition_entries_lba.to_le_bytes())?; |
| out.write_all(&self.num_partition_entries.to_le_bytes())?; |
| out.write_all(&self.partition_entry_size.to_le_bytes())?; |
| out.write_all(&self.partition_entries_crc32.to_le_bytes())?; |
| 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], |
| } |
| |
| // TODO: Derive this once arrays of more than 32 elements have default values. |
| 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)?; |
| write_guid(out, self.unique_partition_guid)?; |
| out.write_all(&self.first_lba.to_le_bytes())?; |
| out.write_all(&self.last_lba.to_le_bytes())?; |
| out.write_all(&self.attributes.to_le_bytes())?; |
| for code_unit in &self.partition_name { |
| out.write_all(&code_unit.to_le_bytes())?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Write a UUID in the mixed-endian format which GPT uses for GUIDs. |
| fn write_guid(out: &mut impl Write, guid: Uuid) -> Result<(), 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); |
| } |
| } |