mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-05 10:10:41 +00:00
Support composite disks.
This adds a new disk file type next to raw files and qcow images that represent an indirection to further raw disk files. The disk file itself is a proto file with references to file paths for other disks to open and their virtual offsets and lengths. The intention is to make it easy to assemble a single virtual hard disk out of several distinct partition files. In the particular case of Cuttlefish running Android in a VM, this is relevant as the Android build system distributes partitions as separate raw files. While the simple solution is to pass each partition as a separate raw disk, some functionality (like the bootloader) assumes there is a partition table with multiple distinct partitions on a single disk. Implementing composite disk support in the VMM bridges this gap through supporting the general-purpose case of a disk built out of multiple component files. If desired, this can be extended to support qcow files to support unusual configurations like a mixed qcow/raw disk. Enabled with the "composite-disk" feature. Bug: b/133432409 Change-Id: I2b0c47d92fab13b5dc0ca5a960c7cfd2b7145b87 Signed-off-by: Cody Schuffelen <schuffelen@google.com> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1667767 Reviewed-by: Daniel Verkamp <dverkamp@chromium.org> Commit-Queue: Daniel Verkamp <dverkamp@chromium.org> Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
parent
b5237bbcf0
commit
f9b035d50c
10 changed files with 636 additions and 0 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -171,7 +171,10 @@ dependencies = [
|
|||
name = "disk"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"data_model 0.1.0",
|
||||
"libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"protobuf 2.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"protos 0.1.0",
|
||||
"qcow 0.1.0",
|
||||
"remain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sys_util 0.1.0",
|
||||
|
|
|
@ -37,6 +37,7 @@ tpm = ["devices/tpm"]
|
|||
wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"]
|
||||
x = ["devices/x"]
|
||||
virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"]
|
||||
composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
|
||||
|
||||
[dependencies]
|
||||
arch = { path = "arch" }
|
||||
|
|
|
@ -7,8 +7,14 @@ edition = "2018"
|
|||
[lib]
|
||||
path = "src/disk.rs"
|
||||
|
||||
[features]
|
||||
composite-disk = ["data_model", "protos", "protobuf"]
|
||||
|
||||
[dependencies]
|
||||
libc = "*"
|
||||
protobuf = { version = "2.3", optional = true }
|
||||
remain = "*"
|
||||
data_model = { path = "../data_model", optional = true }
|
||||
protos = { path = "../protos", optional = true }
|
||||
qcow = { path = "../qcow" }
|
||||
sys_util = { path = "../sys_util" }
|
||||
|
|
566
disk/src/composite.rs
Normal file
566
disk/src/composite.rs
Normal file
|
@ -0,0 +1,566 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
use std::cmp::{max, min};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, ErrorKind, Read, Seek, SeekFrom};
|
||||
use std::ops::Range;
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use crate::{create_disk_file, DiskFile, ImageType};
|
||||
use data_model::VolatileSlice;
|
||||
use protos::cdisk_spec;
|
||||
use remain::sorted;
|
||||
use sys_util::{AsRawFds, FileReadWriteVolatile, FileSetLen, FileSync, PunchHole, WriteZeroes};
|
||||
|
||||
#[sorted]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
DiskError(Box<crate::Error>),
|
||||
InvalidMagicHeader,
|
||||
InvalidProto(protobuf::ProtobufError),
|
||||
InvalidSpecification(String),
|
||||
OpenFile(io::Error),
|
||||
ReadSpecificationError(io::Error),
|
||||
UnknownVersion(u64),
|
||||
UnsupportedComponent(ImageType),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
#[remain::check]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
#[sorted]
|
||||
match self {
|
||||
DiskError(e) => write!(f, "failed to use underlying disk: \"{}\"", e),
|
||||
InvalidMagicHeader => write!(f, "invalid magic header for composite disk format"),
|
||||
InvalidProto(e) => write!(f, "failed to parse specification proto: \"{}\"", e),
|
||||
InvalidSpecification(s) => write!(f, "invalid specification: \"{}\"", s),
|
||||
OpenFile(e) => write!(f, "failed to open component file: \"{}\"", e),
|
||||
ReadSpecificationError(e) => write!(f, "failed to read specification: \"{}\"", e),
|
||||
UnknownVersion(v) => write!(f, "unknown version {} in specification", v),
|
||||
UnsupportedComponent(c) => write!(f, "unsupported component disk type \"{:?}\"", c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
struct ComponentDiskPart {
|
||||
file: Box<dyn DiskFile>,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
}
|
||||
|
||||
impl ComponentDiskPart {
|
||||
fn range(&self) -> Range<u64> {
|
||||
self.offset..(self.offset + self.length)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a composite virtual disk made out of multiple component files. This is described on
|
||||
/// disk by a protocol buffer file that lists out the component file locations and their offsets
|
||||
/// and lengths on the virtual disk. The spaces covered by the component disks must be contiguous
|
||||
/// and not overlapping.
|
||||
pub struct CompositeDiskFile {
|
||||
component_disks: Vec<ComponentDiskPart>,
|
||||
cursor_location: u64,
|
||||
}
|
||||
|
||||
fn ranges_overlap(a: &Range<u64>, b: &Range<u64>) -> bool {
|
||||
// essentially !range_intersection(a, b).is_empty(), but that's experimental
|
||||
let intersection = range_intersection(a, b);
|
||||
intersection.start < intersection.end
|
||||
}
|
||||
|
||||
fn range_intersection(a: &Range<u64>, b: &Range<u64>) -> Range<u64> {
|
||||
Range {
|
||||
start: max(a.start, b.start),
|
||||
end: min(a.end, b.end),
|
||||
}
|
||||
}
|
||||
|
||||
/// A magic string placed at the beginning of a composite disk file to identify it.
|
||||
pub static 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;
|
||||
|
||||
impl CompositeDiskFile {
|
||||
fn new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile> {
|
||||
disks.sort_by(|d1, d2| d1.offset.cmp(&d2.offset));
|
||||
let contiguous_err = disks
|
||||
.windows(2)
|
||||
.map(|s| {
|
||||
if s[0].offset == s[1].offset {
|
||||
let text = format!("Two disks at offset {}", s[0].offset);
|
||||
Err(Error::InvalidSpecification(text))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.find(|r| r.is_err());
|
||||
if let Some(Err(e)) = contiguous_err {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(CompositeDiskFile {
|
||||
component_disks: disks,
|
||||
cursor_location: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set up a composite disk by reading the specification from a file. The file must consist of
|
||||
/// the CDISK_MAGIC string followed by one binary instance of the CompositeDisk protocol
|
||||
/// buffer. Returns an error if it could not read the file or if the specification was invalid.
|
||||
pub fn from_file(mut file: File) -> Result<CompositeDiskFile> {
|
||||
file.seek(SeekFrom::Start(0))
|
||||
.map_err(Error::ReadSpecificationError)?;
|
||||
let mut magic_space = [0u8; CDISK_MAGIC_LEN];
|
||||
file.read_exact(&mut magic_space[..])
|
||||
.map_err(Error::ReadSpecificationError)?;
|
||||
if magic_space != CDISK_MAGIC.as_bytes() {
|
||||
return Err(Error::InvalidMagicHeader);
|
||||
}
|
||||
let proto: cdisk_spec::CompositeDisk =
|
||||
protobuf::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
|
||||
if proto.get_version() != 1 {
|
||||
return Err(Error::UnknownVersion(proto.get_version()));
|
||||
}
|
||||
let mut open_options = OpenOptions::new();
|
||||
open_options.read(true);
|
||||
let mut disks: Vec<ComponentDiskPart> = proto
|
||||
.get_component_disks()
|
||||
.into_iter()
|
||||
.map(|disk| {
|
||||
open_options.write(
|
||||
disk.get_read_write_capability() == cdisk_spec::ReadWriteCapability::READ_WRITE,
|
||||
);
|
||||
let file = open_options
|
||||
.open(disk.get_file_path())
|
||||
.map_err(Error::OpenFile)?;
|
||||
Ok(ComponentDiskPart {
|
||||
file: create_disk_file(file).map_err(|e| Error::DiskError(Box::new(e)))?,
|
||||
offset: disk.get_offset(),
|
||||
length: 0, // Assigned later
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<ComponentDiskPart>>>()?;
|
||||
disks.sort_by(|d1, d2| d1.offset.cmp(&d2.offset));
|
||||
for i in 0..(disks.len() - 1) {
|
||||
let length = disks[i + 1].offset - disks[i].offset;
|
||||
if length == 0 {
|
||||
let text = format!("Two disks at offset {}", disks[i].offset);
|
||||
return Err(Error::InvalidSpecification(text));
|
||||
}
|
||||
if let Some(disk) = disks.get_mut(i) {
|
||||
disk.length = length;
|
||||
} else {
|
||||
let text = format!("Unable to set disk length {}", length);
|
||||
return Err(Error::InvalidSpecification(text));
|
||||
}
|
||||
}
|
||||
let num_disks = disks.len();
|
||||
if let Some(last_disk) = disks.get_mut(num_disks - 1) {
|
||||
if proto.get_length() <= last_disk.offset {
|
||||
let text = format!(
|
||||
"Full size of disk doesn't match last offset. {} <= {}",
|
||||
proto.get_length(),
|
||||
last_disk.offset
|
||||
);
|
||||
return Err(Error::InvalidSpecification(text));
|
||||
}
|
||||
last_disk.length = proto.get_length() - last_disk.offset;
|
||||
} else {
|
||||
let text = format!(
|
||||
"Unable to set last disk length to end at {}",
|
||||
proto.get_length()
|
||||
);
|
||||
return Err(Error::InvalidSpecification(text));
|
||||
}
|
||||
|
||||
CompositeDiskFile::new(disks)
|
||||
}
|
||||
|
||||
fn length(&self) -> u64 {
|
||||
if let Some(disk) = self.component_disks.last() {
|
||||
disk.offset + disk.length
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn disk_at_offset<'a>(&'a mut self, offset: u64) -> io::Result<&'a mut ComponentDiskPart> {
|
||||
self.component_disks
|
||||
.iter_mut()
|
||||
.find(|disk| disk.range().contains(&offset))
|
||||
.ok_or(io::Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("no disk at offset {}", offset),
|
||||
))
|
||||
}
|
||||
|
||||
fn disks_in_range<'a>(&'a mut self, range: &Range<u64>) -> Vec<&'a mut ComponentDiskPart> {
|
||||
self.component_disks
|
||||
.iter_mut()
|
||||
.filter(|disk| ranges_overlap(&disk.range(), range))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSetLen for CompositeDiskFile {
|
||||
fn set_len(&self, _len: u64) -> io::Result<()> {
|
||||
Err(io::Error::new(ErrorKind::Other, "unsupported operation"))
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSync for CompositeDiskFile {
|
||||
fn fsync(&mut self) -> io::Result<()> {
|
||||
for disk in self.component_disks.iter_mut() {
|
||||
disk.file.fsync()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Read and Write targeting volatile storage for composite disks.
|
||||
//
|
||||
// Note that reads and writes will return early if crossing component disk boundaries.
|
||||
// This is allowed by the read and write specifications, which only say read and write
|
||||
// have to return how many bytes were actually read or written. Use read_exact_volatile
|
||||
// or write_all_volatile to make sure all bytes are received/transmitted.
|
||||
//
|
||||
// If one of the component disks does a partial read or write, that also gets passed
|
||||
// transparently to the parent.
|
||||
impl FileReadWriteVolatile for CompositeDiskFile {
|
||||
fn read_volatile(&mut self, slice: VolatileSlice) -> io::Result<usize> {
|
||||
let cursor_location = self.cursor_location;
|
||||
let disk = self.disk_at_offset(cursor_location)?;
|
||||
disk.file
|
||||
.seek(SeekFrom::Start(cursor_location - disk.offset))?;
|
||||
let subslice = if cursor_location + slice.size() > disk.offset + disk.length {
|
||||
let new_size = disk.offset + disk.length - cursor_location;
|
||||
slice
|
||||
.sub_slice(0, new_size)
|
||||
.map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
|
||||
} else {
|
||||
slice
|
||||
};
|
||||
let result = disk.file.read_volatile(subslice);
|
||||
if let Ok(size) = result {
|
||||
self.cursor_location += size as u64;
|
||||
}
|
||||
result
|
||||
}
|
||||
fn write_volatile(&mut self, slice: VolatileSlice) -> io::Result<usize> {
|
||||
let cursor_location = self.cursor_location;
|
||||
let disk = self.disk_at_offset(cursor_location)?;
|
||||
disk.file
|
||||
.seek(SeekFrom::Start(cursor_location - disk.offset))?;
|
||||
let subslice = if cursor_location + slice.size() > disk.offset + disk.length {
|
||||
let new_size = disk.offset + disk.length - cursor_location;
|
||||
slice
|
||||
.sub_slice(0, new_size)
|
||||
.map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
|
||||
} else {
|
||||
slice
|
||||
};
|
||||
let result = disk.file.write_volatile(subslice);
|
||||
if let Ok(size) = result {
|
||||
self.cursor_location += size as u64;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl PunchHole for CompositeDiskFile {
|
||||
fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
|
||||
let range = offset..(offset + length);
|
||||
let disks = self.disks_in_range(&range);
|
||||
for disk in disks {
|
||||
let intersection = range_intersection(&range, &disk.range());
|
||||
if intersection.start >= intersection.end {
|
||||
continue;
|
||||
}
|
||||
let result = disk.file.punch_hole(
|
||||
intersection.start - disk.offset,
|
||||
intersection.end - intersection.start,
|
||||
);
|
||||
if result.is_err() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for CompositeDiskFile {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
let cursor_location = match pos {
|
||||
SeekFrom::Start(offset) => Ok(offset),
|
||||
SeekFrom::End(offset) => u64::try_from(self.length() as i64 + offset),
|
||||
SeekFrom::Current(offset) => u64::try_from(self.cursor_location as i64 + offset),
|
||||
}
|
||||
.map_err(|e| io::Error::new(ErrorKind::InvalidData, e))?;
|
||||
self.cursor_location = cursor_location;
|
||||
Ok(cursor_location)
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteZeroes for CompositeDiskFile {
|
||||
fn write_zeroes(&mut self, length: usize) -> io::Result<usize> {
|
||||
let cursor_location = self.cursor_location;
|
||||
let disk = self.disk_at_offset(cursor_location)?;
|
||||
disk.file
|
||||
.seek(SeekFrom::Start(cursor_location - disk.offset))?;
|
||||
let new_length = if cursor_location + length as u64 > disk.offset + disk.length {
|
||||
(disk.offset + disk.length - cursor_location) as usize
|
||||
} else {
|
||||
length
|
||||
};
|
||||
let result = disk.file.write_zeroes(new_length);
|
||||
if let Ok(size) = result {
|
||||
self.cursor_location += size as u64;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFds for CompositeDiskFile {
|
||||
fn as_raw_fds(&self) -> Vec<RawFd> {
|
||||
self.component_disks
|
||||
.iter()
|
||||
.map(|d| d.file.as_raw_fds())
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use data_model::VolatileMemory;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use sys_util::SharedMemory;
|
||||
|
||||
#[test]
|
||||
fn block_duplicate_offset_disks() {
|
||||
let file1: File = SharedMemory::new(None).unwrap().into();
|
||||
let file2: File = SharedMemory::new(None).unwrap().into();
|
||||
let disk_part1 = ComponentDiskPart {
|
||||
file: Box::new(file1),
|
||||
offset: 0,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part2 = ComponentDiskPart {
|
||||
file: Box::new(file2),
|
||||
offset: 0,
|
||||
length: 100,
|
||||
};
|
||||
assert!(CompositeDiskFile::new(vec![disk_part1, disk_part2]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seek_to_end() {
|
||||
let file1: File = SharedMemory::new(None).unwrap().into();
|
||||
let file2: File = SharedMemory::new(None).unwrap().into();
|
||||
let disk_part1 = ComponentDiskPart {
|
||||
file: Box::new(file1),
|
||||
offset: 0,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part2 = ComponentDiskPart {
|
||||
file: Box::new(file2),
|
||||
offset: 100,
|
||||
length: 100,
|
||||
};
|
||||
let mut composite = CompositeDiskFile::new(vec![disk_part1, disk_part2]).unwrap();
|
||||
let location = composite.seek(SeekFrom::End(0)).unwrap();
|
||||
assert_eq!(location, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_file_passthrough() {
|
||||
let file: File = SharedMemory::new(None).unwrap().into();
|
||||
let disk_part = ComponentDiskPart {
|
||||
file: Box::new(file),
|
||||
offset: 0,
|
||||
length: 100,
|
||||
};
|
||||
let mut composite = CompositeDiskFile::new(vec![disk_part]).unwrap();
|
||||
let mut input_memory = [55u8; 5];
|
||||
let input_volatile_memory = &mut input_memory[..];
|
||||
composite
|
||||
.write_all_volatile(input_volatile_memory.get_slice(0, 5).unwrap())
|
||||
.unwrap();
|
||||
composite.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut output_memory = [0u8; 5];
|
||||
let output_volatile_memory = &mut output_memory[..];
|
||||
composite
|
||||
.read_exact_volatile(output_volatile_memory.get_slice(0, 5).unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(input_memory, output_memory);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triple_file_fds() {
|
||||
let file1: File = SharedMemory::new(None).unwrap().into();
|
||||
let file2: File = SharedMemory::new(None).unwrap().into();
|
||||
let file3: File = SharedMemory::new(None).unwrap().into();
|
||||
let mut in_fds = vec![file1.as_raw_fd(), file2.as_raw_fd(), file3.as_raw_fd()];
|
||||
in_fds.sort();
|
||||
let disk_part1 = ComponentDiskPart {
|
||||
file: Box::new(file1),
|
||||
offset: 0,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part2 = ComponentDiskPart {
|
||||
file: Box::new(file2),
|
||||
offset: 100,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part3 = ComponentDiskPart {
|
||||
file: Box::new(file3),
|
||||
offset: 200,
|
||||
length: 100,
|
||||
};
|
||||
let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
|
||||
let mut out_fds = composite.as_raw_fds();
|
||||
out_fds.sort();
|
||||
assert_eq!(in_fds, out_fds);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triple_file_passthrough() {
|
||||
let file1: File = SharedMemory::new(None).unwrap().into();
|
||||
let file2: File = SharedMemory::new(None).unwrap().into();
|
||||
let file3: File = SharedMemory::new(None).unwrap().into();
|
||||
let disk_part1 = ComponentDiskPart {
|
||||
file: Box::new(file1),
|
||||
offset: 0,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part2 = ComponentDiskPart {
|
||||
file: Box::new(file2),
|
||||
offset: 100,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part3 = ComponentDiskPart {
|
||||
file: Box::new(file3),
|
||||
offset: 200,
|
||||
length: 100,
|
||||
};
|
||||
let mut composite =
|
||||
CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
|
||||
composite.seek(SeekFrom::Start(50)).unwrap();
|
||||
let mut input_memory = [55u8; 200];
|
||||
let input_volatile_memory = &mut input_memory[..];
|
||||
composite
|
||||
.write_all_volatile(input_volatile_memory.get_slice(0, 200).unwrap())
|
||||
.unwrap();
|
||||
composite.seek(SeekFrom::Start(50)).unwrap();
|
||||
let mut output_memory = [0u8; 200];
|
||||
let output_volatile_memory = &mut output_memory[..];
|
||||
composite
|
||||
.read_exact_volatile(output_volatile_memory.get_slice(0, 200).unwrap())
|
||||
.unwrap();
|
||||
assert!(input_memory.into_iter().eq(output_memory.into_iter()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triple_file_punch_hole() {
|
||||
let file1: File = SharedMemory::new(None).unwrap().into();
|
||||
let file2: File = SharedMemory::new(None).unwrap().into();
|
||||
let file3: File = SharedMemory::new(None).unwrap().into();
|
||||
let disk_part1 = ComponentDiskPart {
|
||||
file: Box::new(file1),
|
||||
offset: 0,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part2 = ComponentDiskPart {
|
||||
file: Box::new(file2),
|
||||
offset: 100,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part3 = ComponentDiskPart {
|
||||
file: Box::new(file3),
|
||||
offset: 200,
|
||||
length: 100,
|
||||
};
|
||||
let mut composite =
|
||||
CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
|
||||
composite.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut input_memory = [55u8; 300];
|
||||
let input_volatile_memory = &mut input_memory[..];
|
||||
composite
|
||||
.write_all_volatile(input_volatile_memory.get_slice(0, 300).unwrap())
|
||||
.unwrap();
|
||||
composite.punch_hole(50, 200).unwrap();
|
||||
composite.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut output_memory = [0u8; 300];
|
||||
let output_volatile_memory = &mut output_memory[..];
|
||||
composite
|
||||
.read_exact_volatile(output_volatile_memory.get_slice(0, 300).unwrap())
|
||||
.unwrap();
|
||||
|
||||
for i in 50..250 {
|
||||
input_memory[i] = 0;
|
||||
}
|
||||
assert!(input_memory.into_iter().eq(output_memory.into_iter()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triple_file_write_zeroes() {
|
||||
let file1: File = SharedMemory::new(None).unwrap().into();
|
||||
let file2: File = SharedMemory::new(None).unwrap().into();
|
||||
let file3: File = SharedMemory::new(None).unwrap().into();
|
||||
let disk_part1 = ComponentDiskPart {
|
||||
file: Box::new(file1),
|
||||
offset: 0,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part2 = ComponentDiskPart {
|
||||
file: Box::new(file2),
|
||||
offset: 100,
|
||||
length: 100,
|
||||
};
|
||||
let disk_part3 = ComponentDiskPart {
|
||||
file: Box::new(file3),
|
||||
offset: 200,
|
||||
length: 100,
|
||||
};
|
||||
let mut composite =
|
||||
CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
|
||||
composite.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut input_memory = [55u8; 300];
|
||||
let input_volatile_memory = &mut input_memory[..];
|
||||
composite
|
||||
.write_all_volatile(input_volatile_memory.get_slice(0, 300).unwrap())
|
||||
.unwrap();
|
||||
composite.seek(SeekFrom::Start(50)).unwrap();
|
||||
let mut zeroes_written = 0;
|
||||
while zeroes_written < 200 {
|
||||
zeroes_written += composite.write_zeroes(200 - zeroes_written).unwrap();
|
||||
}
|
||||
composite.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut output_memory = [0u8; 300];
|
||||
let output_volatile_memory = &mut output_memory[..];
|
||||
composite
|
||||
.read_exact_volatile(output_volatile_memory.get_slice(0, 300).unwrap())
|
||||
.unwrap();
|
||||
|
||||
for i in 50..250 {
|
||||
input_memory[i] = 0;
|
||||
}
|
||||
for i in 0..300 {
|
||||
println!(
|
||||
"input[{0}] = {1}, output[{0}] = {2}",
|
||||
i, input_memory[i], output_memory[i]
|
||||
);
|
||||
}
|
||||
assert!(input_memory.into_iter().eq(output_memory.into_iter()));
|
||||
}
|
||||
}
|
|
@ -14,10 +14,18 @@ use sys_util::{
|
|||
AsRawFds, FileReadWriteVolatile, FileSetLen, FileSync, PunchHole, SeekHole, WriteZeroes,
|
||||
};
|
||||
|
||||
#[cfg(feature = "composite-disk")]
|
||||
mod composite;
|
||||
#[cfg(feature = "composite-disk")]
|
||||
use composite::{CompositeDiskFile, CDISK_MAGIC, CDISK_MAGIC_LEN};
|
||||
|
||||
#[sorted]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BlockDeviceNew(sys_util::Error),
|
||||
ConversionNotSupported,
|
||||
#[cfg(feature = "composite-disk")]
|
||||
CreateCompositeDisk(composite::Error),
|
||||
QcowError(qcow::Error),
|
||||
ReadingData(io::Error),
|
||||
ReadingHeader(io::Error),
|
||||
|
@ -55,6 +63,9 @@ impl Display for Error {
|
|||
#[sorted]
|
||||
match self {
|
||||
BlockDeviceNew(e) => write!(f, "failed to create block device: {}", e),
|
||||
ConversionNotSupported => write!(f, "requested file conversion not supported"),
|
||||
#[cfg(feature = "composite-disk")]
|
||||
CreateCompositeDisk(e) => write!(f, "failure in composite disk: {}", e),
|
||||
QcowError(e) => write!(f, "failure in qcow: {}", e),
|
||||
ReadingData(e) => write!(f, "failed to read data: {}", e),
|
||||
ReadingHeader(e) => write!(f, "failed to read header: {}", e),
|
||||
|
@ -71,6 +82,7 @@ impl Display for Error {
|
|||
pub enum ImageType {
|
||||
Raw,
|
||||
Qcow2,
|
||||
CompositeDisk,
|
||||
}
|
||||
|
||||
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
|
||||
|
@ -159,6 +171,7 @@ where
|
|||
.map_err(Error::SettingFileSize)?;
|
||||
convert_reader_writer(reader, &mut dst_writer, src_size)
|
||||
}
|
||||
_ => Err(Error::ConversionNotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,6 +190,8 @@ pub fn convert(src_file: File, dst_file: File, dst_type: ImageType) -> Result<()
|
|||
let mut src_reader = src_file;
|
||||
convert_reader(&mut src_reader, dst_file, dst_type)
|
||||
}
|
||||
// TODO(schuffelen): Implement Read + Write + SeekHole for CompositeDiskFile
|
||||
_ => Err(Error::ConversionNotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,6 +203,18 @@ pub fn detect_image_type(file: &File) -> Result<ImageType> {
|
|||
let mut magic = [0u8; 4];
|
||||
f.read_exact(&mut magic).map_err(Error::ReadingHeader)?;
|
||||
let magic = u32::from_be_bytes(magic);
|
||||
#[cfg(feature = "composite-disk")]
|
||||
{
|
||||
f.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
|
||||
let mut cdisk_magic = [0u8; CDISK_MAGIC_LEN];
|
||||
f.read_exact(&mut cdisk_magic[..])
|
||||
.map_err(Error::ReadingHeader)?;
|
||||
if cdisk_magic == CDISK_MAGIC.as_bytes() {
|
||||
f.seek(SeekFrom::Start(orig_seek))
|
||||
.map_err(Error::SeekingFile)?;
|
||||
return Ok(ImageType::CompositeDisk);
|
||||
}
|
||||
}
|
||||
let image_type = if magic == QCOW_MAGIC {
|
||||
ImageType::Qcow2
|
||||
} else {
|
||||
|
@ -206,5 +233,13 @@ pub fn create_disk_file(raw_image: File) -> Result<Box<dyn DiskFile>> {
|
|||
ImageType::Qcow2 => {
|
||||
Box::new(QcowFile::from(raw_image).map_err(Error::QcowError)?) as Box<dyn DiskFile>
|
||||
}
|
||||
#[cfg(feature = "composite-disk")]
|
||||
ImageType::CompositeDisk => {
|
||||
// Valid composite disk header present
|
||||
Box::new(CompositeDiskFile::from_file(raw_image).map_err(Error::CreateCompositeDisk)?)
|
||||
as Box<dyn DiskFile>
|
||||
}
|
||||
#[cfg(not(feature = "composite-disk"))]
|
||||
ImageType::CompositeDisk => return Err(Error::UnknownType),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2018"
|
|||
|
||||
[features]
|
||||
plugin = ["kvm_sys"]
|
||||
composite-disk = []
|
||||
trunks = []
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -48,6 +48,8 @@ struct LocalProto {
|
|||
static LOCAL_PROTOS: &[LocalProto] = &[
|
||||
#[cfg(feature = "plugin")]
|
||||
LocalProto { module: "plugin" },
|
||||
#[cfg(feature = "composite-disk")]
|
||||
LocalProto { module: "cdisk_spec" },
|
||||
];
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
|
18
protos/src/cdisk_spec.proto
Normal file
18
protos/src/cdisk_spec.proto
Normal file
|
@ -0,0 +1,18 @@
|
|||
syntax = "proto3";
|
||||
|
||||
enum ReadWriteCapability {
|
||||
READ_ONLY = 0;
|
||||
READ_WRITE = 1;
|
||||
}
|
||||
|
||||
message ComponentDisk {
|
||||
string file_path = 1;
|
||||
uint64 offset = 2;
|
||||
ReadWriteCapability read_write_capability = 3;
|
||||
}
|
||||
|
||||
message CompositeDisk {
|
||||
uint64 version = 1;
|
||||
repeated ComponentDisk component_disks = 2;
|
||||
uint64 length = 3;
|
||||
};
|
|
@ -11,3 +11,6 @@ pub mod plugin;
|
|||
|
||||
#[cfg(feature = "trunks")]
|
||||
pub use generated::trunks;
|
||||
|
||||
#[cfg(feature = "composite-disk")]
|
||||
pub use generated::cdisk_spec;
|
||||
|
|
|
@ -83,6 +83,7 @@ pub unsafe extern "C" fn expand_disk_image(path: *const c_char, virtual_size: u6
|
|||
Ok(f) => Box::new(f),
|
||||
Err(_) => return -EINVAL,
|
||||
},
|
||||
_ => return -EINVAL,
|
||||
};
|
||||
|
||||
// For safety against accidentally shrinking the disk image due to a
|
||||
|
|
Loading…
Reference in a new issue