mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-05 18:20:34 +00:00
Add methods to create composite disk image.
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>
This commit is contained in:
parent
da1f83c0b3
commit
fb7c108f15
6 changed files with 919 additions and 10 deletions
47
Cargo.lock
generated
47
Cargo.lock
generated
|
@ -151,6 +151,12 @@ version = "0.1.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
|
@ -168,6 +174,15 @@ dependencies = [
|
|||
"data_model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cros_async"
|
||||
version = "0.1.0"
|
||||
|
@ -340,6 +355,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"async-trait",
|
||||
"base",
|
||||
"crc32fast",
|
||||
"cros_async",
|
||||
"data_model",
|
||||
"futures",
|
||||
|
@ -349,6 +365,7 @@ dependencies = [
|
|||
"remain",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"vm_memory",
|
||||
]
|
||||
|
||||
|
@ -455,7 +472,7 @@ version = "0.4.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224c17cf54ffe7e084343f25c7f2881a399bea69862ecaf5bc97f0f6586ba0dc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 0.1.10",
|
||||
"log",
|
||||
"managed",
|
||||
"num-traits",
|
||||
|
@ -471,6 +488,17 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpu_display"
|
||||
version = "0.1.0"
|
||||
|
@ -635,7 +663,7 @@ version = "0.4.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1138,6 +1166,15 @@ dependencies = [
|
|||
"usb_sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vfio_sys"
|
||||
version = "0.1.0"
|
||||
|
@ -1227,6 +1264,12 @@ dependencies = [
|
|||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
|
@ -8,19 +8,21 @@ edition = "2018"
|
|||
path = "src/disk.rs"
|
||||
|
||||
[features]
|
||||
composite-disk = ["protos", "protobuf"]
|
||||
composite-disk = ["crc32fast", "protos", "protobuf", "uuid"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.36"
|
||||
base = { path = "../base" }
|
||||
crc32fast = { version = "1.2.1", optional = true }
|
||||
libc = "*"
|
||||
protobuf = { version = "2.3", optional = true }
|
||||
remain = "*"
|
||||
tempfile = "*"
|
||||
thiserror = "*"
|
||||
uuid = { version = "0.8.2", features = ["v4"], optional = true }
|
||||
cros_async = { path = "../cros_async" }
|
||||
data_model = { path = "../data_model" }
|
||||
protos = { path = "../protos", optional = true }
|
||||
protos = { path = "../protos", features = ["composite-disk"], optional = true }
|
||||
vm_memory = { path = "../vm_memory" }
|
||||
|
||||
[dependencies.futures]
|
||||
|
|
|
@ -3,31 +3,64 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, ErrorKind, Read, Seek, SeekFrom};
|
||||
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::{create_disk_file, DiskFile, DiskGetLen, ImageType};
|
||||
use base::{
|
||||
AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync, PunchHole,
|
||||
RawDescriptor, WriteZeroesAt,
|
||||
};
|
||||
use crc32fast::Hasher;
|
||||
use data_model::VolatileSlice;
|
||||
use protos::cdisk_spec;
|
||||
use protobuf::Message;
|
||||
use protos::cdisk_spec::{self, ComponentDisk, CompositeDisk, ReadWriteCapability};
|
||||
use remain::sorted;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::gpt::{
|
||||
self, write_gpt_header, write_protective_mbr, GptPartitionEntry, GPT_BEGINNING_SIZE,
|
||||
GPT_END_SIZE, GPT_HEADER_SIZE, GPT_NUM_PARTITIONS, GPT_PARTITION_ENTRY_SIZE, SECTOR_SIZE,
|
||||
};
|
||||
use crate::{create_disk_file, DiskFile, DiskGetLen, ImageType};
|
||||
|
||||
/// The amount of padding needed between the last partition entry and the first partition, to align
|
||||
/// the partition appropriately. The two sectors are for the MBR and the GPT header.
|
||||
const PARTITION_ALIGNMENT_SIZE: usize = GPT_BEGINNING_SIZE as usize
|
||||
- 2 * SECTOR_SIZE as usize
|
||||
- GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize;
|
||||
const HEADER_PADDING_LENGTH: usize = SECTOR_SIZE as usize - GPT_HEADER_SIZE as usize;
|
||||
// Keep all partitions 4k aligned for performance.
|
||||
const PARTITION_SIZE_SHIFT: u8 = 12;
|
||||
// Keep the disk size a multiple of 64k for crosvm's virtio_blk driver.
|
||||
const DISK_SIZE_SHIFT: u8 = 16;
|
||||
|
||||
// From https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs.
|
||||
const LINUX_FILESYSTEM_GUID: Uuid = Uuid::from_u128(0x0FC63DAF_8483_4772_8E79_3D69D8477DE4);
|
||||
const EFI_SYSTEM_PARTITION_GUID: Uuid = Uuid::from_u128(0xC12A7328_F81F_11D2_BA4B_00A0C93EC93B);
|
||||
|
||||
#[sorted]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
DiskError(Box<crate::Error>),
|
||||
DuplicatePartitionLabel(String),
|
||||
GptError(gpt::Error),
|
||||
InvalidMagicHeader,
|
||||
InvalidPath(PathBuf),
|
||||
InvalidProto(protobuf::ProtobufError),
|
||||
InvalidSpecification(String),
|
||||
NoImageFiles(PartitionInfo),
|
||||
OpenFile(io::Error, String),
|
||||
ReadSpecificationError(io::Error),
|
||||
UnalignedReadWrite(PartitionInfo),
|
||||
UnknownVersion(u64),
|
||||
UnsupportedComponent(ImageType),
|
||||
WriteHeader(io::Error),
|
||||
WriteProto(protobuf::ProtobufError),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
|
@ -38,17 +71,39 @@ impl Display for Error {
|
|||
#[sorted]
|
||||
match self {
|
||||
DiskError(e) => write!(f, "failed to use underlying disk: \"{}\"", e),
|
||||
DuplicatePartitionLabel(label) => {
|
||||
write!(f, "duplicate GPT partition label \"{}\"", label)
|
||||
}
|
||||
GptError(e) => write!(f, "failed to write GPT header: \"{}\"", e),
|
||||
InvalidMagicHeader => write!(f, "invalid magic header for composite disk format"),
|
||||
InvalidPath(path) => write!(f, "invalid partition path {:?}", path),
|
||||
InvalidProto(e) => write!(f, "failed to parse specification proto: \"{}\"", e),
|
||||
InvalidSpecification(s) => write!(f, "invalid specification: \"{}\"", s),
|
||||
NoImageFiles(partition) => write!(f, "no image files for partition {:?}", partition),
|
||||
OpenFile(e, p) => write!(f, "failed to open component file \"{}\": \"{}\"", p, e),
|
||||
ReadSpecificationError(e) => write!(f, "failed to read specification: \"{}\"", e),
|
||||
UnalignedReadWrite(partition) => write!(
|
||||
f,
|
||||
"Read-write partition {:?} size is not a multiple of {}.",
|
||||
partition,
|
||||
1 << PARTITION_SIZE_SHIFT
|
||||
),
|
||||
UnknownVersion(v) => write!(f, "unknown version {} in specification", v),
|
||||
UnsupportedComponent(c) => write!(f, "unsupported component disk type \"{:?}\"", c),
|
||||
WriteHeader(e) => write!(f, "failed to write composite disk header: \"{}\"", e),
|
||||
WriteProto(e) => write!(f, "failed to write specification proto: \"{}\"", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl From<gpt::Error> for Error {
|
||||
fn from(e: gpt::Error) -> Self {
|
||||
Self::GptError(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -86,11 +141,14 @@ fn range_intersection(a: &Range<u64>, b: &Range<u64>) -> Range<u64> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The version of the composite disk format supported by this implementation.
|
||||
const COMPOSITE_DISK_VERSION: u64 = 1;
|
||||
|
||||
/// A magic string placed at the beginning of a composite disk file to identify it.
|
||||
pub static CDISK_MAGIC: &str = "composite_disk\x1d";
|
||||
pub const CDISK_MAGIC: &str = "composite_disk\x1d";
|
||||
/// The length of the CDISK_MAGIC string. Created explicitly as a static constant so that it is
|
||||
/// possible to create a character array of the same length.
|
||||
pub const CDISK_MAGIC_LEN: usize = 15;
|
||||
pub const CDISK_MAGIC_LEN: usize = CDISK_MAGIC.len();
|
||||
|
||||
impl CompositeDiskFile {
|
||||
fn new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile> {
|
||||
|
@ -128,7 +186,7 @@ impl CompositeDiskFile {
|
|||
}
|
||||
let proto: cdisk_spec::CompositeDisk =
|
||||
protobuf::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
|
||||
if proto.get_version() != 1 {
|
||||
if proto.get_version() != COMPOSITE_DISK_VERSION {
|
||||
return Err(Error::UnknownVersion(proto.get_version()));
|
||||
}
|
||||
let mut open_options = OpenOptions::new();
|
||||
|
@ -339,9 +397,280 @@ impl AsRawDescriptors for CompositeDiskFile {
|
|||
}
|
||||
}
|
||||
|
||||
/// Information about a single image file to be included in a partition.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PartitionFileInfo {
|
||||
pub path: PathBuf,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
/// Information about a partition to create, including the set of image files which make it up.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PartitionInfo {
|
||||
pub label: String,
|
||||
pub files: Vec<PartitionFileInfo>,
|
||||
pub partition_type: ImagePartitionType,
|
||||
pub writable: bool,
|
||||
}
|
||||
|
||||
/// Round `val` up to the next multiple of 2**`align_log`.
|
||||
fn align_to_power_of_2(val: u64, align_log: u8) -> u64 {
|
||||
let align = 1 << align_log;
|
||||
((val + (align - 1)) / align) * align
|
||||
}
|
||||
|
||||
impl PartitionInfo {
|
||||
fn aligned_size(&self) -> u64 {
|
||||
align_to_power_of_2(
|
||||
self.files.iter().map(|file| file.size).sum(),
|
||||
PARTITION_SIZE_SHIFT,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of partition.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ImagePartitionType {
|
||||
LinuxFilesystem,
|
||||
EfiSystemPartition,
|
||||
}
|
||||
|
||||
impl ImagePartitionType {
|
||||
fn guid(self) -> Uuid {
|
||||
match self {
|
||||
Self::LinuxFilesystem => LINUX_FILESYSTEM_GUID,
|
||||
Self::EfiSystemPartition => EFI_SYSTEM_PARTITION_GUID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write protective MBR and primary GPT table.
|
||||
fn write_beginning(
|
||||
file: &mut impl Write,
|
||||
disk_guid: Uuid,
|
||||
partitions: &[u8],
|
||||
partition_entries_crc32: u32,
|
||||
secondary_table_offset: u64,
|
||||
disk_size: u64,
|
||||
) -> Result<()> {
|
||||
// Write the protective MBR to the first sector.
|
||||
write_protective_mbr(file, disk_size)?;
|
||||
|
||||
// Write the GPT header, and pad out to the end of the sector.
|
||||
write_gpt_header(
|
||||
file,
|
||||
disk_guid,
|
||||
partition_entries_crc32,
|
||||
secondary_table_offset,
|
||||
false,
|
||||
)?;
|
||||
file.write_all(&[0; HEADER_PADDING_LENGTH])
|
||||
.map_err(Error::WriteHeader)?;
|
||||
|
||||
// Write partition entries, including unused ones.
|
||||
file.write_all(partitions).map_err(Error::WriteHeader)?;
|
||||
|
||||
// Write zeroes to align the first partition appropriately.
|
||||
file.write_all(&[0; PARTITION_ALIGNMENT_SIZE])
|
||||
.map_err(Error::WriteHeader)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write secondary GPT table.
|
||||
fn write_end(
|
||||
file: &mut impl Write,
|
||||
disk_guid: Uuid,
|
||||
partitions: &[u8],
|
||||
partition_entries_crc32: u32,
|
||||
secondary_table_offset: u64,
|
||||
disk_size: u64,
|
||||
) -> Result<()> {
|
||||
// Write partition entries, including unused ones.
|
||||
file.write_all(partitions).map_err(Error::WriteHeader)?;
|
||||
|
||||
// Write the GPT header, and pad out to the end of the sector.
|
||||
write_gpt_header(
|
||||
file,
|
||||
disk_guid,
|
||||
partition_entries_crc32,
|
||||
secondary_table_offset,
|
||||
true,
|
||||
)?;
|
||||
file.write_all(&[0; HEADER_PADDING_LENGTH])
|
||||
.map_err(Error::WriteHeader)?;
|
||||
|
||||
// Pad out to the aligned disk size.
|
||||
let used_disk_size = secondary_table_offset + GPT_END_SIZE;
|
||||
let padding = disk_size - used_disk_size;
|
||||
file.write_all(&vec![0; padding as usize])
|
||||
.map_err(Error::WriteHeader)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create the `GptPartitionEntry` for the given partition.
|
||||
fn create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry {
|
||||
let mut partition_name: Vec<u16> = partition.label.encode_utf16().collect();
|
||||
partition_name.resize(36, 0);
|
||||
|
||||
GptPartitionEntry {
|
||||
partition_type_guid: partition.partition_type.guid(),
|
||||
unique_partition_guid: Uuid::new_v4(),
|
||||
first_lba: offset / SECTOR_SIZE,
|
||||
last_lba: (offset + partition.aligned_size()) / SECTOR_SIZE - 1,
|
||||
attributes: 0,
|
||||
partition_name: partition_name.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create one or more `ComponentDisk` proto messages for the given partition.
|
||||
fn create_component_disks(
|
||||
partition: &PartitionInfo,
|
||||
offset: u64,
|
||||
header_path: &str,
|
||||
) -> Result<Vec<ComponentDisk>> {
|
||||
let aligned_size = partition.aligned_size();
|
||||
|
||||
if partition.files.is_empty() {
|
||||
return Err(Error::NoImageFiles(partition.to_owned()));
|
||||
}
|
||||
let mut file_size_sum = 0;
|
||||
let mut component_disks = vec![];
|
||||
for file in &partition.files {
|
||||
component_disks.push(ComponentDisk {
|
||||
offset: offset + file_size_sum,
|
||||
file_path: file
|
||||
.path
|
||||
.to_str()
|
||||
.ok_or_else(|| Error::InvalidPath(file.path.to_owned()))?
|
||||
.to_string(),
|
||||
read_write_capability: if partition.writable {
|
||||
ReadWriteCapability::READ_WRITE
|
||||
} else {
|
||||
ReadWriteCapability::READ_ONLY
|
||||
},
|
||||
..ComponentDisk::new()
|
||||
});
|
||||
file_size_sum += file.size;
|
||||
}
|
||||
|
||||
if file_size_sum != aligned_size {
|
||||
if partition.writable {
|
||||
return Err(Error::UnalignedReadWrite(partition.to_owned()));
|
||||
} else {
|
||||
// Fill in the gap by reusing the header file, because we know it is always bigger
|
||||
// than the alignment size (i.e. GPT_BEGINNING_SIZE > 1 << PARTITION_SIZE_SHIFT).
|
||||
component_disks.push(ComponentDisk {
|
||||
offset: offset + file_size_sum,
|
||||
file_path: header_path.to_owned(),
|
||||
read_write_capability: ReadWriteCapability::READ_ONLY,
|
||||
..ComponentDisk::new()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(component_disks)
|
||||
}
|
||||
|
||||
/// Create a new composite disk image containing the given partitions, and write it out to the given
|
||||
/// files.
|
||||
pub fn create_composite_disk(
|
||||
partitions: &[PartitionInfo],
|
||||
header_path: &Path,
|
||||
header_file: &mut File,
|
||||
footer_path: &Path,
|
||||
footer_file: &mut File,
|
||||
output_composite: &mut File,
|
||||
) -> Result<()> {
|
||||
let header_path = header_path
|
||||
.to_str()
|
||||
.ok_or_else(|| Error::InvalidPath(header_path.to_owned()))?
|
||||
.to_string();
|
||||
let footer_path = footer_path
|
||||
.to_str()
|
||||
.ok_or_else(|| Error::InvalidPath(footer_path.to_owned()))?
|
||||
.to_string();
|
||||
|
||||
let mut composite_proto = CompositeDisk::new();
|
||||
composite_proto.version = COMPOSITE_DISK_VERSION;
|
||||
composite_proto.component_disks.push(ComponentDisk {
|
||||
file_path: header_path.clone(),
|
||||
offset: 0,
|
||||
read_write_capability: ReadWriteCapability::READ_ONLY,
|
||||
..ComponentDisk::new()
|
||||
});
|
||||
|
||||
// Write partitions to a temporary buffer so that we can calculate the CRC, and construct the
|
||||
// ComponentDisk proto messages at the same time.
|
||||
let mut partitions_buffer =
|
||||
[0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
|
||||
let mut writer: &mut [u8] = &mut partitions_buffer;
|
||||
let mut next_disk_offset = GPT_BEGINNING_SIZE;
|
||||
let mut labels = HashSet::with_capacity(partitions.len());
|
||||
for partition in partitions {
|
||||
let gpt_entry = create_gpt_entry(partition, next_disk_offset);
|
||||
if !labels.insert(gpt_entry.partition_name) {
|
||||
return Err(Error::DuplicatePartitionLabel(partition.label.clone()));
|
||||
}
|
||||
gpt_entry.write_bytes(&mut writer)?;
|
||||
|
||||
for component_disk in create_component_disks(partition, next_disk_offset, &header_path)? {
|
||||
composite_proto.component_disks.push(component_disk);
|
||||
}
|
||||
|
||||
next_disk_offset += partition.aligned_size();
|
||||
}
|
||||
let secondary_table_offset = next_disk_offset;
|
||||
let disk_size = align_to_power_of_2(secondary_table_offset + GPT_END_SIZE, DISK_SIZE_SHIFT);
|
||||
|
||||
composite_proto.component_disks.push(ComponentDisk {
|
||||
file_path: footer_path,
|
||||
offset: secondary_table_offset,
|
||||
read_write_capability: ReadWriteCapability::READ_ONLY,
|
||||
..ComponentDisk::new()
|
||||
});
|
||||
|
||||
// Calculate CRC32 of partition entries.
|
||||
let mut hasher = Hasher::new();
|
||||
hasher.update(&partitions_buffer);
|
||||
let partition_entries_crc32 = hasher.finalize();
|
||||
|
||||
let disk_guid = Uuid::new_v4();
|
||||
write_beginning(
|
||||
header_file,
|
||||
disk_guid,
|
||||
&partitions_buffer,
|
||||
partition_entries_crc32,
|
||||
secondary_table_offset,
|
||||
disk_size,
|
||||
)?;
|
||||
write_end(
|
||||
footer_file,
|
||||
disk_guid,
|
||||
&partitions_buffer,
|
||||
partition_entries_crc32,
|
||||
secondary_table_offset,
|
||||
disk_size,
|
||||
)?;
|
||||
|
||||
composite_proto.length = disk_size;
|
||||
output_composite
|
||||
.write_all(CDISK_MAGIC.as_bytes())
|
||||
.map_err(Error::WriteHeader)?;
|
||||
composite_proto
|
||||
.write_to_writer(output_composite)
|
||||
.map_err(Error::WriteProto)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::matches;
|
||||
|
||||
use base::AsRawDescriptor;
|
||||
use data_model::VolatileMemory;
|
||||
use tempfile::tempfile;
|
||||
|
@ -561,4 +890,151 @@ mod tests {
|
|||
}
|
||||
assert!(input_memory.iter().eq(output_memory.iter()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beginning_size() {
|
||||
let mut buffer = vec![];
|
||||
let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
|
||||
let disk_size = 1000 * SECTOR_SIZE;
|
||||
write_beginning(
|
||||
&mut buffer,
|
||||
Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
|
||||
&partitions,
|
||||
42,
|
||||
disk_size - GPT_END_SIZE,
|
||||
disk_size,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end_size() {
|
||||
let mut buffer = vec![];
|
||||
let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
|
||||
let disk_size = 1000 * SECTOR_SIZE;
|
||||
write_end(
|
||||
&mut buffer,
|
||||
Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
|
||||
&partitions,
|
||||
42,
|
||||
disk_size - GPT_END_SIZE,
|
||||
disk_size,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(buffer.len(), GPT_END_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end_size_with_padding() {
|
||||
let mut buffer = vec![];
|
||||
let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
|
||||
let disk_size = 1000 * SECTOR_SIZE;
|
||||
let padding = 3 * SECTOR_SIZE;
|
||||
write_end(
|
||||
&mut buffer,
|
||||
Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
|
||||
&partitions,
|
||||
42,
|
||||
disk_size - GPT_END_SIZE - padding,
|
||||
disk_size,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
|
||||
}
|
||||
|
||||
/// Creates a composite disk image with no partitions.
|
||||
#[test]
|
||||
fn create_composite_disk_empty() {
|
||||
let mut header_image = tempfile().unwrap();
|
||||
let mut footer_image = tempfile().unwrap();
|
||||
let mut composite_image = tempfile().unwrap();
|
||||
|
||||
create_composite_disk(
|
||||
&[],
|
||||
Path::new("/header_path.img"),
|
||||
&mut header_image,
|
||||
Path::new("/footer_path.img"),
|
||||
&mut footer_image,
|
||||
&mut composite_image,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Creates a composite disk image with two partitions.
|
||||
#[test]
|
||||
fn create_composite_disk_success() {
|
||||
let mut header_image = tempfile().unwrap();
|
||||
let mut footer_image = tempfile().unwrap();
|
||||
let mut composite_image = tempfile().unwrap();
|
||||
|
||||
create_composite_disk(
|
||||
&[
|
||||
PartitionInfo {
|
||||
label: "partition1".to_string(),
|
||||
files: vec![PartitionFileInfo {
|
||||
path: "/partition1.img".to_string().into(),
|
||||
size: 0,
|
||||
}],
|
||||
partition_type: ImagePartitionType::LinuxFilesystem,
|
||||
writable: false,
|
||||
},
|
||||
PartitionInfo {
|
||||
label: "partition2".to_string(),
|
||||
files: vec![PartitionFileInfo {
|
||||
path: "/partition2.img".to_string().into(),
|
||||
size: 0,
|
||||
}],
|
||||
partition_type: ImagePartitionType::LinuxFilesystem,
|
||||
writable: true,
|
||||
},
|
||||
],
|
||||
Path::new("/header_path.img"),
|
||||
&mut header_image,
|
||||
Path::new("/footer_path.img"),
|
||||
&mut footer_image,
|
||||
&mut composite_image,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Attempts to create a composite disk image with two partitions with the same label.
|
||||
#[test]
|
||||
fn create_composite_disk_duplicate_label() {
|
||||
let mut header_image = tempfile().unwrap();
|
||||
let mut footer_image = tempfile().unwrap();
|
||||
let mut composite_image = tempfile().unwrap();
|
||||
|
||||
let result = create_composite_disk(
|
||||
&[
|
||||
PartitionInfo {
|
||||
label: "label".to_string(),
|
||||
files: vec![PartitionFileInfo {
|
||||
path: "/partition1.img".to_string().into(),
|
||||
size: 0,
|
||||
}],
|
||||
partition_type: ImagePartitionType::LinuxFilesystem,
|
||||
writable: false,
|
||||
},
|
||||
PartitionInfo {
|
||||
label: "label".to_string(),
|
||||
files: vec![PartitionFileInfo {
|
||||
path: "/partition2.img".to_string().into(),
|
||||
size: 0,
|
||||
}],
|
||||
partition_type: ImagePartitionType::LinuxFilesystem,
|
||||
writable: true,
|
||||
},
|
||||
],
|
||||
Path::new("/header_path.img"),
|
||||
&mut header_image,
|
||||
Path::new("/footer_path.img"),
|
||||
&mut footer_image,
|
||||
&mut composite_image,
|
||||
);
|
||||
assert!(matches!(result, Err(Error::DuplicatePartitionLabel(label)) if label == "label"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,15 @@ pub use qcow::{QcowFile, QCOW_MAGIC};
|
|||
mod composite;
|
||||
#[cfg(feature = "composite-disk")]
|
||||
use composite::{CompositeDiskFile, CDISK_MAGIC, CDISK_MAGIC_LEN};
|
||||
#[cfg(feature = "composite-disk")]
|
||||
mod gpt;
|
||||
#[cfg(feature = "composite-disk")]
|
||||
pub use composite::{
|
||||
create_composite_disk, Error as CompositeError, ImagePartitionType, PartitionFileInfo,
|
||||
PartitionInfo,
|
||||
};
|
||||
#[cfg(feature = "composite-disk")]
|
||||
pub use gpt::Error as GptError;
|
||||
|
||||
mod android_sparse;
|
||||
use android_sparse::{AndroidSparse, SPARSE_HEADER_MAGIC};
|
||||
|
|
275
disk/src/gpt.rs
Normal file
275
disk/src/gpt.rs
Normal file
|
@ -0,0 +1,275 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
104
src/main.rs
104
src/main.rs
|
@ -36,6 +36,10 @@ use devices::ProtectionType;
|
|||
#[cfg(feature = "audio")]
|
||||
use devices::{Ac97Backend, Ac97Parameters};
|
||||
use disk::QcowFile;
|
||||
#[cfg(feature = "composite-disk")]
|
||||
use disk::{
|
||||
create_composite_disk, create_disk_file, ImagePartitionType, PartitionFileInfo, PartitionInfo,
|
||||
};
|
||||
use vm_control::{
|
||||
client::{
|
||||
do_modify_battery, do_usb_attach, do_usb_detach, do_usb_list, handle_request, vms_request,
|
||||
|
@ -2239,6 +2243,102 @@ fn balloon_stats(mut args: std::env::Args) -> std::result::Result<(), ()> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "composite-disk")]
|
||||
fn create_composite(mut args: std::env::Args) -> std::result::Result<(), ()> {
|
||||
if args.len() < 1 {
|
||||
print_help("crosvm create_composite", "PATH [LABEL:PARTITION]..", &[]);
|
||||
println!("Creates a new composite disk image containing the given partition images");
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let composite_image_path = args.next().unwrap();
|
||||
let header_path = format!("{}.header", composite_image_path);
|
||||
let footer_path = format!("{}.footer", composite_image_path);
|
||||
|
||||
let mut composite_image_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&composite_image_path)
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Failed opening composite disk image file at '{}': {}",
|
||||
composite_image_path, e
|
||||
);
|
||||
})?;
|
||||
let mut header_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&header_path)
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Failed opening header image file at '{}': {}",
|
||||
header_path, e
|
||||
);
|
||||
})?;
|
||||
let mut footer_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&footer_path)
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Failed opening footer image file at '{}': {}",
|
||||
footer_path, e
|
||||
);
|
||||
})?;
|
||||
|
||||
let partitions = args
|
||||
.into_iter()
|
||||
.map(|partition_arg| {
|
||||
if let [label, path] = partition_arg.split(":").collect::<Vec<_>>()[..] {
|
||||
let partition_file = File::open(path)
|
||||
.map_err(|e| error!("Failed to open partition image: {}", e))?;
|
||||
let size = create_disk_file(partition_file)
|
||||
.map_err(|e| error!("Failed to create DiskFile instance: {}", e))?
|
||||
.get_len()
|
||||
.map_err(|e| error!("Failed to get length of partition image: {}", e))?;
|
||||
Ok(PartitionInfo {
|
||||
label: label.to_owned(),
|
||||
files: vec![PartitionFileInfo {
|
||||
path: Path::new(path).to_owned(),
|
||||
size,
|
||||
}],
|
||||
partition_type: ImagePartitionType::LinuxFilesystem,
|
||||
writable: false,
|
||||
})
|
||||
} else {
|
||||
error!(
|
||||
"Must specify label and path for partition '{}', like LABEL:PATH",
|
||||
partition_arg
|
||||
);
|
||||
Err(())
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
create_composite_disk(
|
||||
&partitions,
|
||||
&PathBuf::from(header_path),
|
||||
&mut header_file,
|
||||
&PathBuf::from(footer_path),
|
||||
&mut footer_file,
|
||||
&mut composite_image_file,
|
||||
)
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Failed to create composite disk image at '{}': {}",
|
||||
composite_image_path, e
|
||||
);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> {
|
||||
let arguments = [
|
||||
Argument::positional("PATH", "where to create the qcow2 image"),
|
||||
|
@ -2459,6 +2559,8 @@ fn print_usage() {
|
|||
println!(" balloon - Set balloon size of the crosvm instance.");
|
||||
println!(" balloon_stats - Prints virtio balloon statistics.");
|
||||
println!(" battery - Modify battery.");
|
||||
#[cfg(feature = "composite-disk")]
|
||||
println!(" create_composite - Create a new composite disk image file.");
|
||||
println!(" create_qcow2 - Create a new qcow2 disk image file.");
|
||||
println!(" disk - Manage attached virtual disk devices.");
|
||||
println!(" resume - Resumes the crosvm instance.");
|
||||
|
@ -2526,6 +2628,8 @@ fn crosvm_main() -> std::result::Result<(), ()> {
|
|||
Some("run") => run_vm(args),
|
||||
Some("balloon") => balloon_vms(args),
|
||||
Some("balloon_stats") => balloon_stats(args),
|
||||
#[cfg(feature = "composite-disk")]
|
||||
Some("create_composite") => create_composite(args),
|
||||
Some("create_qcow2") => create_qcow2(args),
|
||||
Some("disk") => disk_cmd(args),
|
||||
Some("usb") => modify_usb(args),
|
||||
|
|
Loading…
Reference in a new issue