Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/disk/src/gpt.rs
5394 views
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.
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 {
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).
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 {
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
209
impl 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.
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]
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
}
277
278