Andrew Walbran | 3eca16c | 2021-06-14 11:15:14 +0000 | [diff] [blame^] | 1 | // 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 | |
| 17 | use anyhow::Error; |
| 18 | use crc32fast::Hasher; |
| 19 | use std::convert::TryInto; |
| 20 | use std::io::Write; |
| 21 | use uuid::Uuid; |
| 22 | |
| 23 | /// The size in bytes of a disk sector (also called a block). |
| 24 | pub const SECTOR_SIZE: u64 = 1 << 9; |
| 25 | /// The size in bytes on an MBR partition entry. |
| 26 | const MBR_PARTITION_ENTRY_SIZE: usize = 16; |
| 27 | /// The size in bytes of a GPT header. |
| 28 | pub 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. |
| 31 | pub const GPT_NUM_PARTITIONS: u32 = 128; |
| 32 | /// The size in bytes of a single GPT partition entry. |
| 33 | pub 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. |
| 36 | pub 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. |
| 39 | pub 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. |
| 45 | pub 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)] |
| 72 | struct 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 | |
| 88 | impl 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). |
| 116 | pub 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)] |
| 155 | pub 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. |
| 166 | impl 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 | |
| 179 | impl 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. |
| 196 | fn 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)] |
| 207 | mod 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 | } |