mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-06 18:38:01 +00:00
This can be used like: crosvm create_composite composite.img partiton1:partition1.img partition2:partition2.img BUG=b:190503456 TEST=cargo test Cq-Depend: chromium:2982777, chromium:3008399 Change-Id: I31a9afe1e5f1e2a850ce1f892122150bcf3441b4 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2972869 Tested-by: kokoro <noreply+kokoro@google.com> Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org> Reviewed-by: Chirantan Ekbote <chirantan@chromium.org> Commit-Queue: Keiichi Watanabe <keiichiw@chromium.org> Auto-Submit: Andrew Walbran <qwandor@google.com>
275 lines
9.1 KiB
Rust
275 lines
9.1 KiB
Rust
// 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);
|
|
}
|
|
}
|