blob: 3e09b6c355ef9c8a64fcf53e1d6694dbc01c7ced [file] [log] [blame]
// 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::collections::HashSet;
use std::convert::TryInto;
use std::fs::{File, OpenOptions};
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
use std::ops::Range;
use std::path::{Path, PathBuf};
use base::{
open_file, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync,
PunchHole, RawDescriptor, WriteZeroesAt,
};
use crc32fast::Hasher;
use data_model::VolatileSlice;
use protobuf::Message;
use protos::cdisk_spec::{self, ComponentDisk, CompositeDisk, ReadWriteCapability};
use remain::sorted;
use thiserror::Error;
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(Error, Debug)]
pub enum Error {
#[error("failed to use underlying disk: \"{0}\"")]
DiskError(Box<crate::Error>),
#[error("duplicate GPT partition label \"{0}\"")]
DuplicatePartitionLabel(String),
#[error("failed to write GPT header: \"{0}\"")]
GptError(gpt::Error),
#[error("invalid magic header for composite disk format")]
InvalidMagicHeader,
#[error("invalid partition path {0:?}")]
InvalidPath(PathBuf),
#[error("failed to parse specification proto: \"{0}\"")]
InvalidProto(protobuf::ProtobufError),
#[error("invalid specification: \"{0}\"")]
InvalidSpecification(String),
#[error("no image files for partition {0:?}")]
NoImageFiles(PartitionInfo),
#[error("failed to open component file \"{1}\": \"{0}\"")]
OpenFile(io::Error, String),
#[error("failed to read specification: \"{0}\"")]
ReadSpecificationError(io::Error),
#[error("Read-write partition {0:?} size is not a multiple of {}.", 1 << PARTITION_SIZE_SHIFT)]
UnalignedReadWrite(PartitionInfo),
#[error("unknown version {0} in specification")]
UnknownVersion(u64),
#[error("unsupported component disk type \"{0:?}\"")]
UnsupportedComponent(ImageType),
#[error("failed to write composite disk header: \"{0}\"")]
WriteHeader(io::Error),
#[error("failed to write specification proto: \"{0}\"")]
WriteProto(protobuf::ProtobufError),
#[error("failed to write zero filler: \"{0}\"")]
WriteZeroFiller(io::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)]
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.
#[derive(Debug)]
pub struct CompositeDiskFile {
component_disks: Vec<ComponentDiskPart>,
}
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),
}
}
/// The version of the composite disk format supported by this implementation.
const COMPOSITE_DISK_VERSION: u64 = 2;
/// A magic string placed at the beginning of a composite disk file to identify it.
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 = CDISK_MAGIC.len();
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,
})
}
/// 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,
max_nesting_depth: u32,
image_path: &Path,
) -> 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 =
Message::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
if proto.get_version() > COMPOSITE_DISK_VERSION {
return Err(Error::UnknownVersion(proto.get_version()));
}
let mut disks: Vec<ComponentDiskPart> = proto
.get_component_disks()
.iter()
.map(|disk| {
let path = if proto.get_version() == 1 {
PathBuf::from(disk.get_file_path())
} else {
image_path.parent().unwrap().join(disk.get_file_path())
};
let comp_file = open_file(
&path,
OpenOptions::new().read(true).write(
disk.get_read_write_capability()
== cdisk_spec::ReadWriteCapability::READ_WRITE,
), // TODO(b/190435784): add support for O_DIRECT.
)
.map_err(|e| Error::OpenFile(e.into(), disk.get_file_path().to_string()))?;
Ok(ComponentDiskPart {
file: create_disk_file(comp_file, max_nesting_depth, &path)
.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(&mut self, offset: u64) -> io::Result<&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 DiskGetLen for CompositeDiskFile {
fn get_len(&self) -> io::Result<u64> {
Ok(self.length())
}
}
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 FileReadWriteAtVolatile for CompositeDiskFile {
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
let cursor_location = offset;
let disk = self.disk_at_offset(cursor_location)?;
let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
let new_size = disk.offset + disk.length - cursor_location;
slice
.sub_slice(0, new_size as usize)
.map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
} else {
slice
};
disk.file
.read_at_volatile(subslice, cursor_location - disk.offset)
}
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
let cursor_location = offset;
let disk = self.disk_at_offset(cursor_location)?;
let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
let new_size = disk.offset + disk.length - cursor_location;
slice
.sub_slice(0, new_size as usize)
.map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
} else {
slice
};
disk.file
.write_at_volatile(subslice, cursor_location - disk.offset)
}
}
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,
);
result?;
}
Ok(())
}
}
impl FileAllocate for CompositeDiskFile {
fn allocate(&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.allocate(
intersection.start - disk.offset,
intersection.end - intersection.start,
);
result?;
}
Ok(())
}
}
impl WriteZeroesAt for CompositeDiskFile {
fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
let cursor_location = offset;
let disk = self.disk_at_offset(cursor_location)?;
let offset_within_disk = 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
};
disk.file.write_zeroes_at(offset_within_disk, new_length)
}
}
impl AsRawDescriptors for CompositeDiskFile {
fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
self.component_disks
.iter()
.flat_map(|d| d.file.as_raw_descriptors())
.collect()
}
}
/// Information about a partition to create.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PartitionInfo {
pub label: String,
pub path: PathBuf,
pub partition_type: ImagePartitionType,
pub writable: bool,
pub size: u64,
}
/// 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.size, 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,
zero_filler_path: &str,
) -> Result<Vec<ComponentDisk>> {
let aligned_size = partition.aligned_size();
let mut component_disks = vec![ComponentDisk {
offset,
file_path: partition
.path
.to_str()
.ok_or_else(|| Error::InvalidPath(partition.path.to_owned()))?
.to_string(),
read_write_capability: if partition.writable {
ReadWriteCapability::READ_WRITE
} else {
ReadWriteCapability::READ_ONLY
},
..ComponentDisk::new()
}];
if partition.size != aligned_size {
if partition.writable {
return Err(Error::UnalignedReadWrite(partition.to_owned()));
} else {
// Fill in the gap by reusing the zero filler file, because we know it is always bigger
// than the alignment size. Its size is 1 << PARTITION_SIZE_SHIFT (4k).
component_disks.push(ComponentDisk {
offset: offset + partition.size,
file_path: zero_filler_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],
zero_filler_path: &Path,
header_path: &Path,
header_file: &mut File,
footer_path: &Path,
footer_file: &mut File,
output_composite: &mut File,
) -> Result<()> {
let zero_filler_path = zero_filler_path
.to_str()
.ok_or_else(|| Error::InvalidPath(zero_filler_path.to_owned()))?
.to_string();
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,
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, &zero_filler_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(())
}
/// Create a zero filler file which can be used to fill the gaps between partition files.
/// The filler is sized to be big enough to fill the gaps. (1 << PARTITION_SIZE_SHIFT)
pub fn create_zero_filler<P: AsRef<Path>>(zero_filler_path: P) -> Result<()> {
let f = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(true)
.open(zero_filler_path.as_ref())
.map_err(Error::WriteZeroFiller)?;
f.set_len(1 << PARTITION_SIZE_SHIFT)
.map_err(Error::WriteZeroFiller)
}
#[cfg(test)]
mod tests {
use super::*;
use std::matches;
use base::AsRawDescriptor;
use data_model::VolatileMemory;
use tempfile::tempfile;
#[test]
fn block_duplicate_offset_disks() {
let file1 = tempfile().unwrap();
let file2 = tempfile().unwrap();
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 get_len() {
let file1 = tempfile().unwrap();
let file2 = tempfile().unwrap();
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 composite = CompositeDiskFile::new(vec![disk_part1, disk_part2]).unwrap();
let len = composite.get_len().unwrap();
assert_eq!(len, 200);
}
#[test]
fn single_file_passthrough() {
let file = tempfile().unwrap();
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 = VolatileSlice::new(&mut input_memory[..]);
composite
.write_all_at_volatile(input_volatile_memory.get_slice(0, 5).unwrap(), 0)
.unwrap();
let mut output_memory = [0u8; 5];
let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
composite
.read_exact_at_volatile(output_volatile_memory.get_slice(0, 5).unwrap(), 0)
.unwrap();
assert_eq!(input_memory, output_memory);
}
#[test]
fn triple_file_fds() {
let file1 = tempfile().unwrap();
let file2 = tempfile().unwrap();
let file3 = tempfile().unwrap();
let mut in_fds = vec![
file1.as_raw_descriptor(),
file2.as_raw_descriptor(),
file3.as_raw_descriptor(),
];
in_fds.sort_unstable();
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_descriptors();
out_fds.sort_unstable();
assert_eq!(in_fds, out_fds);
}
#[test]
fn triple_file_passthrough() {
let file1 = tempfile().unwrap();
let file2 = tempfile().unwrap();
let file3 = tempfile().unwrap();
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();
let mut input_memory = [55u8; 200];
let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
composite
.write_all_at_volatile(input_volatile_memory.get_slice(0, 200).unwrap(), 50)
.unwrap();
let mut output_memory = [0u8; 200];
let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
composite
.read_exact_at_volatile(output_volatile_memory.get_slice(0, 200).unwrap(), 50)
.unwrap();
assert!(input_memory.iter().eq(output_memory.iter()));
}
#[test]
fn triple_file_punch_hole() {
let file1 = tempfile().unwrap();
let file2 = tempfile().unwrap();
let file3 = tempfile().unwrap();
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();
let mut input_memory = [55u8; 300];
let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
composite
.write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0)
.unwrap();
composite.punch_hole(50, 200).unwrap();
let mut output_memory = [0u8; 300];
let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
composite
.read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
.unwrap();
input_memory[50..250].iter_mut().for_each(|x| *x = 0);
assert!(input_memory.iter().eq(output_memory.iter()));
}
#[test]
fn triple_file_write_zeroes() {
let file1 = tempfile().unwrap();
let file2 = tempfile().unwrap();
let file3 = tempfile().unwrap();
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();
let mut input_memory = [55u8; 300];
let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
composite
.write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0)
.unwrap();
let mut zeroes_written = 0;
while zeroes_written < 200 {
zeroes_written += composite
.write_zeroes_at(50 + zeroes_written as u64, 200 - zeroes_written)
.unwrap();
}
let mut output_memory = [0u8; 300];
let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
composite
.read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
.unwrap();
input_memory[50..250].iter_mut().for_each(|x| *x = 0);
for i in 0..300 {
println!(
"input[{0}] = {1}, output[{0}] = {2}",
i, input_memory[i], output_memory[i]
);
}
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("/zero_filler.img"),
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(),
path: "/partition1.img".to_string().into(),
partition_type: ImagePartitionType::LinuxFilesystem,
writable: false,
size: 0,
},
PartitionInfo {
label: "partition2".to_string(),
path: "/partition2.img".to_string().into(),
partition_type: ImagePartitionType::LinuxFilesystem,
writable: true,
size: 0,
},
],
Path::new("/zero_filler.img"),
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(),
path: "/partition1.img".to_string().into(),
partition_type: ImagePartitionType::LinuxFilesystem,
writable: false,
size: 0,
},
PartitionInfo {
label: "label".to_string(),
path: "/partition2.img".to_string().into(),
partition_type: ImagePartitionType::LinuxFilesystem,
writable: true,
size: 0,
},
],
Path::new("/zero_filler.img"),
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"));
}
}