diff --git a/Cargo.lock b/Cargo.lock index 8017a3408e..b20bee386e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 13a304729f..bd90dcfb75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/disk/Cargo.toml b/disk/Cargo.toml index c26cf4bad6..0847932fed 100644 --- a/disk/Cargo.toml +++ b/disk/Cargo.toml @@ -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" } diff --git a/disk/src/composite.rs b/disk/src/composite.rs new file mode 100644 index 0000000000..5d6e5a2bba --- /dev/null +++ b/disk/src/composite.rs @@ -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), + 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 = std::result::Result; + +struct ComponentDiskPart { + file: Box, + offset: u64, + length: u64, +} + +impl ComponentDiskPart { + fn range(&self) -> Range { + 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, + cursor_location: u64, +} + +fn ranges_overlap(a: &Range, b: &Range) -> 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, b: &Range) -> Range { + 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) -> Result { + 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 { + 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 = 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::>>()?; + 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) -> 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 { + 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 { + 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 { + 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 { + 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 { + 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())); + } +} diff --git a/disk/src/disk.rs b/disk/src/disk.rs index 9f1914d0e3..f63d63cd71 100644 --- a/disk/src/disk.rs +++ b/disk/src/disk.rs @@ -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(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 { 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> { ImageType::Qcow2 => { Box::new(QcowFile::from(raw_image).map_err(Error::QcowError)?) as Box } + #[cfg(feature = "composite-disk")] + ImageType::CompositeDisk => { + // Valid composite disk header present + Box::new(CompositeDiskFile::from_file(raw_image).map_err(Error::CreateCompositeDisk)?) + as Box + } + #[cfg(not(feature = "composite-disk"))] + ImageType::CompositeDisk => return Err(Error::UnknownType), }) } diff --git a/protos/Cargo.toml b/protos/Cargo.toml index 9fd0e5a8a6..98cd4ae2d2 100644 --- a/protos/Cargo.toml +++ b/protos/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [features] plugin = ["kvm_sys"] +composite-disk = [] trunks = [] [dependencies] diff --git a/protos/build.rs b/protos/build.rs index b510e54e52..2e870b48f9 100644 --- a/protos/build.rs +++ b/protos/build.rs @@ -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<()> { diff --git a/protos/src/cdisk_spec.proto b/protos/src/cdisk_spec.proto new file mode 100644 index 0000000000..771c7ceaa4 --- /dev/null +++ b/protos/src/cdisk_spec.proto @@ -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; +}; diff --git a/protos/src/lib.rs b/protos/src/lib.rs index 3fdbdc0756..d73fb0352f 100644 --- a/protos/src/lib.rs +++ b/protos/src/lib.rs @@ -11,3 +11,6 @@ pub mod plugin; #[cfg(feature = "trunks")] pub use generated::trunks; + +#[cfg(feature = "composite-disk")] +pub use generated::cdisk_spec; diff --git a/qcow_utils/src/qcow_utils.rs b/qcow_utils/src/qcow_utils.rs index 4c3ede7e9a..1dcf2a450b 100644 --- a/qcow_utils/src/qcow_utils.rs +++ b/qcow_utils/src/qcow_utils.rs @@ -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