mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: Move VHDX format files into formats/vhdx/
Move VHDX format implementation into a structured directory layout: vhdx/mod.rs -> formats/vhdx/internal/mod.rs (Vhdx) vhdx/vhdx_bat.rs -> formats/vhdx/internal/bat.rs vhdx/vhdx_header.rs -> formats/vhdx/internal/header.rs vhdx/vhdx_io.rs -> formats/vhdx/internal/io.rs vhdx/vhdx_metadata.rs -> formats/vhdx/internal/metadata.rs vhdx_sync.rs -> formats/vhdx/mod.rs (VhdxDisk) Extract VhdxSync from vhdx_sync.rs into formats/vhdx/worker/sync.rs. Drop the vhdx_ prefix from internal file names since the parent directory already provides the namespace. Update all internal cross references to use the new module paths. Re-export formats::vhdx as vhdx_sync in lib.rs for backward compatibility. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
1ca0c39b4d
commit
b595f1dbc3
@@ -9,3 +9,4 @@
|
||||
|
||||
pub mod raw;
|
||||
pub mod vhd;
|
||||
pub mod vhdx;
|
||||
|
||||
106
block/src/formats/vhdx/internal/bat.rs
Normal file
106
block/src/formats/vhdx/internal/bat.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
|
||||
use super::header::RegionTableEntry;
|
||||
use super::metadata::DiskSpec;
|
||||
|
||||
// Payload BAT Entry States
|
||||
pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
|
||||
pub const PAYLOAD_BLOCK_UNDEFINED: u64 = 1;
|
||||
pub const PAYLOAD_BLOCK_ZERO: u64 = 2;
|
||||
pub const PAYLOAD_BLOCK_UNMAPPED: u64 = 3;
|
||||
pub const PAYLOAD_BLOCK_FULLY_PRESENT: u64 = 6;
|
||||
pub const PAYLOAD_BLOCK_PARTIALLY_PRESENT: u64 = 7;
|
||||
|
||||
// Mask for the BAT state
|
||||
pub const BAT_STATE_BIT_MASK: u64 = 0x07;
|
||||
// Mask for the offset within the file in units of 1 MB
|
||||
pub const BAT_FILE_OFF_MASK: u64 = 0xFFFFFFFFFFF00000;
|
||||
|
||||
#[sorted]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum VhdxBatError {
|
||||
#[error("Invalid BAT entry")]
|
||||
InvalidBatEntry,
|
||||
#[error("Invalid BAT entry count")]
|
||||
InvalidEntryCount,
|
||||
#[error("Failed to read BAT entry")]
|
||||
ReadBat(#[source] io::Error),
|
||||
#[error("Failed to write BAT entry")]
|
||||
WriteBat(#[source] io::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, VhdxBatError>;
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct BatEntry(pub u64);
|
||||
|
||||
impl BatEntry {
|
||||
// Read all BAT entries presented on the disk and insert them to a vector
|
||||
pub fn collect_bat_entries(
|
||||
f: &mut File,
|
||||
disk_spec: &DiskSpec,
|
||||
bat_entry: &RegionTableEntry,
|
||||
) -> Result<Vec<BatEntry>> {
|
||||
let entry_count = BatEntry::calculate_entries(
|
||||
disk_spec.block_size,
|
||||
disk_spec.virtual_disk_size,
|
||||
disk_spec.chunk_ratio,
|
||||
);
|
||||
if entry_count as usize > (bat_entry.length as usize / size_of::<BatEntry>()) {
|
||||
return Err(VhdxBatError::InvalidEntryCount);
|
||||
}
|
||||
|
||||
let mut bat: Vec<BatEntry> = Vec::with_capacity(bat_entry.length as usize);
|
||||
let offset = bat_entry.file_offset;
|
||||
for i in 0..entry_count {
|
||||
f.seek(SeekFrom::Start(offset + i * size_of::<u64>() as u64))
|
||||
.map_err(VhdxBatError::ReadBat)?;
|
||||
|
||||
let bat_entry = BatEntry(
|
||||
f.read_u64::<LittleEndian>()
|
||||
.map_err(VhdxBatError::ReadBat)?,
|
||||
);
|
||||
bat.insert(i as usize, bat_entry);
|
||||
}
|
||||
|
||||
Ok(bat)
|
||||
}
|
||||
|
||||
// Calculate the number of entries in the BAT
|
||||
fn calculate_entries(block_size: u32, virtual_disk_size: u64, chunk_ratio: u64) -> u64 {
|
||||
let data_blocks_count = virtual_disk_size.div_ceil(block_size as u64);
|
||||
data_blocks_count + (data_blocks_count - 1) / chunk_ratio
|
||||
}
|
||||
|
||||
// Routine for writing BAT entries to the disk
|
||||
pub fn write_bat_entries(
|
||||
f: &mut File,
|
||||
bat_offset: u64,
|
||||
bat_entries: &[BatEntry],
|
||||
) -> Result<()> {
|
||||
for i in 0..bat_entries.len() as u64 {
|
||||
f.seek(SeekFrom::Start(bat_offset + i * size_of::<u64>() as u64))
|
||||
.map_err(VhdxBatError::WriteBat)?;
|
||||
let bat_entry = match bat_entries.get(i as usize) {
|
||||
Some(entry) => entry.0,
|
||||
None => {
|
||||
return Err(VhdxBatError::InvalidBatEntry);
|
||||
}
|
||||
};
|
||||
|
||||
f.write_u64::<LittleEndian>(bat_entry)
|
||||
.map_err(VhdxBatError::WriteBat)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
468
block/src/formats/vhdx/internal/header.rs
Normal file
468
block/src/formats/vhdx/internal/header.rs
Normal file
@@ -0,0 +1,468 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876; // "vhdxfile"
|
||||
const HEADER_SIGN: u32 = 0x6461_6568; // "head"
|
||||
const REGION_SIGN: u32 = 0x6967_6572; // "regi"
|
||||
|
||||
const FILE_START: u64 = 0; // The first element
|
||||
const HEADER_1_START: u64 = 64 * 1024; // Header 1 start in Bytes
|
||||
const HEADER_2_START: u64 = 128 * 1024; // Header 2 start in Bytes
|
||||
pub const REGION_TABLE_1_START: u64 = 192 * 1024; // Region 1 start in Bytes
|
||||
const REGION_TABLE_2_START: u64 = 256 * 1024; // Region 2 start in Bytes
|
||||
|
||||
const HEADER_SIZE: u64 = 4 * 1024; // Each header is 64 KiB, but only first 4 kiB contains info
|
||||
const REGION_SIZE: u64 = 64 * 1024; // Each region size is 64 KiB
|
||||
|
||||
const REGION_ENTRY_REQUIRED: u32 = 1;
|
||||
|
||||
const BAT_GUID: &str = "2DC27766-F623-4200-9D64-115E9BFD4A08"; // BAT GUID
|
||||
const MDR_GUID: &str = "8B7CA206-4790-4B9A-B8FE-575F050F886E"; // Metadata GUID
|
||||
|
||||
#[sorted]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum VhdxHeaderError {
|
||||
#[error("Failed to calculate checksum")]
|
||||
CalculateChecksum,
|
||||
#[error("BAT entry is not unique")]
|
||||
DuplicateBATEntry,
|
||||
#[error("Metadata region entry is not unique")]
|
||||
DuplicateMDREntry,
|
||||
#[error("Checksum doesn't match for")]
|
||||
InvalidChecksum(String),
|
||||
#[error("Invalid entry count")]
|
||||
InvalidEntryCount,
|
||||
#[error("Not a valid VHDx header")]
|
||||
InvalidHeaderSign,
|
||||
#[error("Not a valid VHDx region")]
|
||||
InvalidRegionSign,
|
||||
#[error("Couldn't parse Uuid for region entry {0}")]
|
||||
InvalidUuid(#[source] uuid::Error),
|
||||
#[error("Not a VHDx file")]
|
||||
InvalidVHDXSign,
|
||||
#[error("No valid header found")]
|
||||
NoValidHeader,
|
||||
#[error("Cannot read checksum")]
|
||||
ReadChecksum,
|
||||
#[error("Failed to read File Type Identifier {0}")]
|
||||
ReadFileTypeIdentifier(#[source] io::Error),
|
||||
#[error("Failed to read headers {0}")]
|
||||
ReadHeader(#[source] io::Error),
|
||||
#[error("Failed to read metadata {0}")]
|
||||
ReadMetadata(#[source] std::io::Error),
|
||||
#[error("Failed to read region table entries {0}")]
|
||||
ReadRegionTableEntries(#[source] io::Error),
|
||||
#[error("Failed to read region table header {0}")]
|
||||
ReadRegionTableHeader(#[source] io::Error),
|
||||
#[error("Failed to read region entries")]
|
||||
RegionEntryCollectionFailed,
|
||||
#[error("Overlapping regions found")]
|
||||
RegionOverlap,
|
||||
#[error("Reserved region has non-zero value")]
|
||||
ReservedIsNonZero,
|
||||
#[error("Failed to seek in File Type Identifier {0}")]
|
||||
SeekFileTypeIdentifier(#[source] io::Error),
|
||||
#[error("Failed to seek in headers {0}")]
|
||||
SeekHeader(#[source] io::Error),
|
||||
#[error("Failed to seek in region table entries {0}")]
|
||||
SeekRegionTableEntries(#[source] io::Error),
|
||||
#[error("Failed to seek in region table header {0}")]
|
||||
SeekRegionTableHeader(#[source] io::Error),
|
||||
#[error("We do not recognize this entry")]
|
||||
UnrecognizedRegionEntry,
|
||||
#[error("Failed to write header {0}")]
|
||||
WriteHeader(#[source] io::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, VhdxHeaderError>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileTypeIdentifier {
|
||||
pub _signature: u64,
|
||||
}
|
||||
|
||||
impl FileTypeIdentifier {
|
||||
/// Reads the File Type Identifier structure from a reference VHDx file
|
||||
pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
|
||||
f.seek(SeekFrom::Start(FILE_START))
|
||||
.map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
|
||||
let _signature = f
|
||||
.read_u64::<LittleEndian>()
|
||||
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
|
||||
if _signature != VHDX_SIGN {
|
||||
return Err(VhdxHeaderError::InvalidVHDXSign);
|
||||
}
|
||||
|
||||
Ok(FileTypeIdentifier { _signature })
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Header {
|
||||
pub signature: u32,
|
||||
pub checksum: u32,
|
||||
pub sequence_number: u64,
|
||||
pub file_write_guid: u128,
|
||||
pub data_write_guid: u128,
|
||||
pub log_guid: u128,
|
||||
pub log_version: u16,
|
||||
pub version: u16,
|
||||
pub log_length: u32,
|
||||
pub log_offset: u64,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
/// Reads the Header structure from a reference VHDx file
|
||||
pub fn new(f: &mut File, start: u64) -> Result<Header> {
|
||||
// Read the whole header into a buffer. We will need it for
|
||||
// calculating checksum.
|
||||
let mut buffer = [0; HEADER_SIZE as usize];
|
||||
f.seek(SeekFrom::Start(start))
|
||||
.map_err(VhdxHeaderError::SeekHeader)?;
|
||||
f.read_exact(&mut buffer)
|
||||
.map_err(VhdxHeaderError::ReadHeader)?;
|
||||
|
||||
// SAFETY: buffer is of correct size and has been successfully filled.
|
||||
let header: Header = unsafe { *(buffer.as_ptr().cast()) };
|
||||
if header.signature != HEADER_SIGN {
|
||||
return Err(VhdxHeaderError::InvalidHeaderSign);
|
||||
}
|
||||
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||
if header.checksum != new_checksum {
|
||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
|
||||
}
|
||||
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
/// Converts the header structure into a buffer
|
||||
fn write_to_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
|
||||
// SAFETY: self is a valid header.
|
||||
let reference =
|
||||
unsafe { std::slice::from_raw_parts((&raw const *self).cast(), HEADER_SIZE as usize) };
|
||||
*buffer = reference.try_into().unwrap();
|
||||
}
|
||||
|
||||
/// Creates and returns new updated header from the provided current header
|
||||
fn update_header(
|
||||
f: &mut File,
|
||||
current_header: &Header,
|
||||
change_data_guid: bool,
|
||||
mut file_write_guid: u128,
|
||||
start: u64,
|
||||
) -> Result<Header> {
|
||||
let mut buffer = [0u8; HEADER_SIZE as usize];
|
||||
let mut data_write_guid = current_header.data_write_guid;
|
||||
|
||||
if change_data_guid {
|
||||
data_write_guid = Uuid::new_v4().as_u128();
|
||||
}
|
||||
|
||||
if file_write_guid == 0 {
|
||||
file_write_guid = current_header.file_write_guid;
|
||||
}
|
||||
|
||||
let mut new_header = Header {
|
||||
signature: current_header.signature,
|
||||
checksum: 0,
|
||||
sequence_number: current_header.sequence_number + 1,
|
||||
file_write_guid,
|
||||
data_write_guid,
|
||||
log_guid: current_header.log_guid,
|
||||
log_version: current_header.log_version,
|
||||
version: current_header.version,
|
||||
log_length: current_header.log_length,
|
||||
log_offset: current_header.log_offset,
|
||||
};
|
||||
|
||||
new_header.write_to_buffer(&mut buffer);
|
||||
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||
new_header.write_to_buffer(&mut buffer);
|
||||
|
||||
f.seek(SeekFrom::Start(start))
|
||||
.map_err(VhdxHeaderError::SeekHeader)?;
|
||||
f.write(&buffer).map_err(VhdxHeaderError::WriteHeader)?;
|
||||
|
||||
Ok(new_header)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct RegionTableHeader {
|
||||
pub signature: u32,
|
||||
pub checksum: u32,
|
||||
pub entry_count: u32,
|
||||
pub reserved: u32,
|
||||
}
|
||||
|
||||
impl RegionTableHeader {
|
||||
/// Reads the Region Table Header structure from a reference VHDx file
|
||||
pub fn new(f: &mut File, start: u64) -> Result<RegionTableHeader> {
|
||||
// Read the whole header into a buffer. We will need it for calculating
|
||||
// checksum.
|
||||
let mut buffer = [0u8; REGION_SIZE as usize];
|
||||
f.seek(SeekFrom::Start(start))
|
||||
.map_err(VhdxHeaderError::SeekRegionTableHeader)?;
|
||||
f.read_exact(&mut buffer)
|
||||
.map_err(VhdxHeaderError::ReadRegionTableHeader)?;
|
||||
|
||||
// SAFETY: buffer is of correct size and has been successfully filled.
|
||||
let region_table_header: RegionTableHeader = unsafe { *(buffer.as_ptr().cast()) };
|
||||
if region_table_header.signature != REGION_SIGN {
|
||||
return Err(VhdxHeaderError::InvalidRegionSign);
|
||||
}
|
||||
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||
if region_table_header.checksum != new_checksum {
|
||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
|
||||
}
|
||||
|
||||
if region_table_header.entry_count > 2047 {
|
||||
return Err(VhdxHeaderError::InvalidEntryCount);
|
||||
}
|
||||
|
||||
if region_table_header.reserved != 0 {
|
||||
return Err(VhdxHeaderError::ReservedIsNonZero);
|
||||
}
|
||||
|
||||
Ok(region_table_header)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegionInfo {
|
||||
pub bat_entry: RegionTableEntry,
|
||||
pub mdr_entry: RegionTableEntry,
|
||||
pub region_entries: BTreeMap<u64, u64>,
|
||||
}
|
||||
|
||||
impl RegionInfo {
|
||||
/// Collect all entries in a BTreeMap from the Region Table and identifies
|
||||
/// BAT and metadata regions
|
||||
pub fn new(f: &mut File, region_start: u64, entry_count: u32) -> Result<RegionInfo> {
|
||||
let mut bat_entry: Option<RegionTableEntry> = None;
|
||||
let mut mdr_entry: Option<RegionTableEntry> = None;
|
||||
|
||||
let mut offset = 0;
|
||||
let mut region_entries = BTreeMap::new();
|
||||
|
||||
let mut buffer = [0; REGION_SIZE as usize];
|
||||
// Seek after the Region Table Header
|
||||
f.seek(SeekFrom::Start(
|
||||
region_start + size_of::<RegionTableHeader>() as u64,
|
||||
))
|
||||
.map_err(VhdxHeaderError::SeekRegionTableEntries)?;
|
||||
f.read_exact(&mut buffer)
|
||||
.map_err(VhdxHeaderError::ReadRegionTableEntries)?;
|
||||
|
||||
for _ in 0..entry_count {
|
||||
let entry =
|
||||
RegionTableEntry::new(&buffer[offset..offset + size_of::<RegionTableEntry>()])?;
|
||||
|
||||
offset += size_of::<RegionTableEntry>();
|
||||
let start = entry.file_offset;
|
||||
let end = start + entry.length as u64;
|
||||
|
||||
for (region_ent_start, region_ent_end) in region_entries.iter() {
|
||||
if !((start >= *region_ent_start) || (end <= *region_ent_end)) {
|
||||
return Err(VhdxHeaderError::RegionOverlap);
|
||||
}
|
||||
}
|
||||
|
||||
region_entries.insert(entry.file_offset, entry.file_offset + entry.length as u64);
|
||||
|
||||
if entry.guid == Uuid::parse_str(BAT_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
|
||||
if bat_entry.is_none() {
|
||||
bat_entry = Some(entry);
|
||||
continue;
|
||||
}
|
||||
return Err(VhdxHeaderError::DuplicateBATEntry);
|
||||
}
|
||||
|
||||
if entry.guid == Uuid::parse_str(MDR_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
|
||||
if mdr_entry.is_none() {
|
||||
mdr_entry = Some(entry);
|
||||
continue;
|
||||
}
|
||||
return Err(VhdxHeaderError::DuplicateMDREntry);
|
||||
}
|
||||
|
||||
if (entry.required & REGION_ENTRY_REQUIRED) == 1 {
|
||||
// This implementation doesn't recognize this field.
|
||||
// Therefore, according to the spec, we are throwing an error.
|
||||
return Err(VhdxHeaderError::UnrecognizedRegionEntry);
|
||||
}
|
||||
}
|
||||
|
||||
if bat_entry.is_none() || mdr_entry.is_none() {
|
||||
region_entries.clear();
|
||||
return Err(VhdxHeaderError::RegionEntryCollectionFailed);
|
||||
}
|
||||
|
||||
// It's safe to unwrap as we checked both entries have been filled.
|
||||
// Otherwise, an error is already returned.
|
||||
let bat_entry = bat_entry.unwrap();
|
||||
let mdr_entry = mdr_entry.unwrap();
|
||||
|
||||
Ok(RegionInfo {
|
||||
bat_entry,
|
||||
mdr_entry,
|
||||
region_entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RegionTableEntry {
|
||||
pub guid: Uuid,
|
||||
pub file_offset: u64,
|
||||
pub length: u32,
|
||||
pub required: u32,
|
||||
}
|
||||
|
||||
impl RegionTableEntry {
|
||||
/// Reads one Region Entry from a Region Table index that starts from 0
|
||||
pub fn new(buffer: &[u8]) -> Result<RegionTableEntry> {
|
||||
assert!(buffer.len() == std::mem::size_of::<RegionTableEntry>());
|
||||
// SAFETY: the assertion above makes sure the buffer size is correct.
|
||||
let mut region_table_entry: RegionTableEntry = unsafe { *(buffer.as_ptr().cast()) };
|
||||
|
||||
let uuid = super::uuid_from_guid(buffer);
|
||||
region_table_entry.guid = uuid;
|
||||
|
||||
Ok(region_table_entry)
|
||||
}
|
||||
}
|
||||
|
||||
enum HeaderNo {
|
||||
First,
|
||||
Second,
|
||||
}
|
||||
|
||||
/// Contains the information from the header of a VHDx file
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VhdxHeader {
|
||||
_file_type_identifier: FileTypeIdentifier,
|
||||
header_1: Header,
|
||||
header_2: Header,
|
||||
region_table_1: RegionTableHeader,
|
||||
_region_table_2: RegionTableHeader,
|
||||
}
|
||||
|
||||
impl VhdxHeader {
|
||||
/// Creates a VhdxHeader from a reference to a file
|
||||
pub fn new(f: &mut File) -> Result<VhdxHeader> {
|
||||
Ok(VhdxHeader {
|
||||
_file_type_identifier: FileTypeIdentifier::new(f)?,
|
||||
header_1: Header::new(f, HEADER_1_START)?,
|
||||
header_2: Header::new(f, HEADER_2_START)?,
|
||||
region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
|
||||
_region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Identify the current header and return both headers along with an
|
||||
/// integer indicating the current header.
|
||||
fn current_header(
|
||||
header_1: Result<Header>,
|
||||
header_2: Result<Header>,
|
||||
) -> Result<(HeaderNo, Header)> {
|
||||
let header_1 = header_1.ok();
|
||||
let header_2 = header_2.ok();
|
||||
|
||||
match (header_1, header_2) {
|
||||
(None, None) => Err(VhdxHeaderError::NoValidHeader),
|
||||
(Some(header_1), None) => Ok((HeaderNo::First, header_1)),
|
||||
(None, Some(header_2)) => Ok((HeaderNo::Second, header_2)),
|
||||
(Some(header_1), Some(header_2)) => {
|
||||
if header_1.sequence_number >= header_2.sequence_number {
|
||||
Ok((HeaderNo::First, header_1))
|
||||
} else {
|
||||
Ok((HeaderNo::Second, header_2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This takes two headers and update the noncurrent header with the
|
||||
/// current one. Returns both headers as a tuple sequenced the way it was
|
||||
/// received from the parameter list.
|
||||
fn update_header(
|
||||
f: &mut File,
|
||||
header_1: Result<Header>,
|
||||
header_2: Result<Header>,
|
||||
guid: u128,
|
||||
) -> Result<(Header, Header)> {
|
||||
let (header_no, current_header) = VhdxHeader::current_header(header_1, header_2)?;
|
||||
|
||||
match header_no {
|
||||
HeaderNo::First => {
|
||||
let other_header =
|
||||
Header::update_header(f, ¤t_header, true, guid, HEADER_2_START)?;
|
||||
Ok((current_header, other_header))
|
||||
}
|
||||
HeaderNo::Second => {
|
||||
let other_header =
|
||||
Header::update_header(f, ¤t_header, true, guid, HEADER_1_START)?;
|
||||
Ok((other_header, current_header))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the provided headers according to the spec
|
||||
fn update_headers(
|
||||
f: &mut File,
|
||||
header_1: Result<Header>,
|
||||
header_2: Result<Header>,
|
||||
guid: u128,
|
||||
) -> Result<(Header, Header)> {
|
||||
// According to the spec, update twice
|
||||
let (header_1, header_2) = VhdxHeader::update_header(f, header_1, header_2, guid)?;
|
||||
VhdxHeader::update_header(f, Ok(header_1), Ok(header_2), guid)
|
||||
}
|
||||
|
||||
pub fn update(&mut self, f: &mut File) -> Result<()> {
|
||||
let headers = VhdxHeader::update_headers(f, Ok(self.header_1), Ok(self.header_2), 0)?;
|
||||
self.header_1 = headers.0;
|
||||
self.header_2 = headers.1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn region_entry_count(&self) -> u32 {
|
||||
self.region_table_1.entry_count
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the checksum of a buffer that itself contains its checksum
|
||||
/// Therefore, before calculating, the existing checksum is retrieved and the
|
||||
/// corresponding field is made zero. After the calculation, the existing checksum
|
||||
/// is put back to the buffer.
|
||||
fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
|
||||
// Read the original checksum from the buffer
|
||||
let orig_csum = LittleEndian::read_u32(&buffer[csum_offset..csum_offset + 4]);
|
||||
// Zero the checksum in the buffer
|
||||
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], 0);
|
||||
// Calculate the checksum on the resulting buffer
|
||||
let mut crc = crc_any::CRC::crc32c();
|
||||
crc.digest(&buffer);
|
||||
let new_csum = crc.get_crc() as u32;
|
||||
|
||||
// Put back the original checksum in the buffer
|
||||
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
|
||||
|
||||
new_csum
|
||||
}
|
||||
218
block/src/formats/vhdx/internal/io.rs
Normal file
218
block/src/formats/vhdx/internal/io.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
|
||||
use super::bat::{self, BatEntry, VhdxBatError};
|
||||
use super::metadata::{self, DiskSpec};
|
||||
|
||||
const SECTOR_SIZE: u64 = 512;
|
||||
|
||||
#[sorted]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum VhdxIoError {
|
||||
#[error("Invalid BAT entry state")]
|
||||
InvalidBatEntryState,
|
||||
#[error("Invalid BAT entry count")]
|
||||
InvalidBatIndex,
|
||||
#[error("Invalid disk size")]
|
||||
InvalidDiskSize,
|
||||
#[error("Failed reading sector blocks from file {0}")]
|
||||
ReadSectorBlock(#[source] io::Error),
|
||||
#[error("Failed changing file length {0}")]
|
||||
ResizeFile(#[source] io::Error),
|
||||
#[error("Differencing mode is not supported yet")]
|
||||
UnsupportedMode,
|
||||
#[error("Failed writing BAT to file {0}")]
|
||||
WriteBat(#[source] VhdxBatError),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, VhdxIoError>;
|
||||
|
||||
macro_rules! align {
|
||||
($n:expr, $align:expr) => {{ $n.div_ceil($align) * $align }};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Sector {
|
||||
bat_index: u64,
|
||||
free_sectors: u64,
|
||||
free_bytes: u64,
|
||||
file_offset: u64,
|
||||
block_offset: u64,
|
||||
}
|
||||
|
||||
impl Sector {
|
||||
/// Translate sector index and count of data in file to actual offsets and
|
||||
/// BAT index.
|
||||
pub fn new(
|
||||
disk_spec: &DiskSpec,
|
||||
bat: &[BatEntry],
|
||||
sector_index: u64,
|
||||
sector_count: u64,
|
||||
) -> Result<Sector> {
|
||||
let mut sector = Sector::default();
|
||||
|
||||
sector.bat_index = sector_index / disk_spec.sectors_per_block as u64;
|
||||
sector.block_offset = sector_index % disk_spec.sectors_per_block as u64;
|
||||
sector.free_sectors = disk_spec.sectors_per_block as u64 - sector.block_offset;
|
||||
if sector.free_sectors > sector_count {
|
||||
sector.free_sectors = sector_count;
|
||||
}
|
||||
|
||||
sector.free_bytes = sector.free_sectors * disk_spec.logical_sector_size as u64;
|
||||
sector.block_offset *= disk_spec.logical_sector_size as u64;
|
||||
|
||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||
Some(entry) => entry.0,
|
||||
None => {
|
||||
return Err(VhdxIoError::InvalidBatIndex);
|
||||
}
|
||||
};
|
||||
sector.file_offset = bat_entry & bat::BAT_FILE_OFF_MASK;
|
||||
if sector.file_offset != 0 {
|
||||
sector.file_offset += sector.block_offset;
|
||||
}
|
||||
|
||||
Ok(sector)
|
||||
}
|
||||
}
|
||||
|
||||
/// VHDx IO read routine: requires relative sector index and count for the
|
||||
/// requested data.
|
||||
pub fn read(
|
||||
f: &mut File,
|
||||
buf: &mut [u8],
|
||||
disk_spec: &DiskSpec,
|
||||
bat: &[BatEntry],
|
||||
mut sector_index: u64,
|
||||
mut sector_count: u64,
|
||||
) -> Result<usize> {
|
||||
if disk_spec.has_parent {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
|
||||
let mut read_count: usize = 0;
|
||||
while sector_count > 0 {
|
||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
||||
|
||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||
Some(entry) => entry.0,
|
||||
None => {
|
||||
return Err(VhdxIoError::InvalidBatIndex);
|
||||
}
|
||||
};
|
||||
|
||||
match bat_entry & bat::BAT_STATE_BIT_MASK {
|
||||
bat::PAYLOAD_BLOCK_NOT_PRESENT
|
||||
| bat::PAYLOAD_BLOCK_UNDEFINED
|
||||
| bat::PAYLOAD_BLOCK_UNMAPPED
|
||||
| bat::PAYLOAD_BLOCK_ZERO => {}
|
||||
bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
||||
f.seek(SeekFrom::Start(sector.file_offset))
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
f.read_exact(
|
||||
&mut buf
|
||||
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||
)
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
}
|
||||
bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
_ => {
|
||||
return Err(VhdxIoError::InvalidBatEntryState);
|
||||
}
|
||||
}
|
||||
sector_count -= sector.free_sectors;
|
||||
sector_index += sector.free_sectors;
|
||||
read_count += sector.free_bytes as usize;
|
||||
}
|
||||
Ok(read_count)
|
||||
}
|
||||
|
||||
/// VHDx IO write routine: requires relative sector index and count for the
|
||||
/// requested data.
|
||||
pub fn write(
|
||||
f: &mut File,
|
||||
buf: &[u8],
|
||||
disk_spec: &mut DiskSpec,
|
||||
bat_offset: u64,
|
||||
bat: &mut [BatEntry],
|
||||
mut sector_index: u64,
|
||||
mut sector_count: u64,
|
||||
) -> Result<usize> {
|
||||
if disk_spec.has_parent {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
|
||||
let mut write_count: usize = 0;
|
||||
while sector_count > 0 {
|
||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
||||
|
||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||
Some(entry) => entry.0,
|
||||
None => {
|
||||
return Err(VhdxIoError::InvalidBatIndex);
|
||||
}
|
||||
};
|
||||
|
||||
match bat_entry & bat::BAT_STATE_BIT_MASK {
|
||||
bat::PAYLOAD_BLOCK_NOT_PRESENT
|
||||
| bat::PAYLOAD_BLOCK_UNDEFINED
|
||||
| bat::PAYLOAD_BLOCK_UNMAPPED
|
||||
| bat::PAYLOAD_BLOCK_ZERO => {
|
||||
let file_offset = align!(disk_spec.image_size, metadata::BLOCK_SIZE_MIN as u64);
|
||||
let new_size = file_offset
|
||||
.checked_add(disk_spec.block_size as u64)
|
||||
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
||||
|
||||
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
|
||||
disk_spec.image_size = new_size;
|
||||
|
||||
let new_bat_entry =
|
||||
file_offset | (bat::PAYLOAD_BLOCK_FULLY_PRESENT & bat::BAT_STATE_BIT_MASK);
|
||||
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
|
||||
BatEntry::write_bat_entries(f, bat_offset, bat).map_err(VhdxIoError::WriteBat)?;
|
||||
|
||||
if file_offset < metadata::BLOCK_SIZE_MIN as u64 {
|
||||
break;
|
||||
}
|
||||
|
||||
f.seek(SeekFrom::Start(file_offset))
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
f.write_all(
|
||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||
)
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
}
|
||||
bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
||||
if sector.file_offset < metadata::BLOCK_SIZE_MIN as u64 {
|
||||
break;
|
||||
}
|
||||
|
||||
f.seek(SeekFrom::Start(sector.file_offset))
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
f.write_all(
|
||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||
)
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
}
|
||||
bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
_ => {
|
||||
return Err(VhdxIoError::InvalidBatEntryState);
|
||||
}
|
||||
}
|
||||
sector_count -= sector.free_sectors;
|
||||
sector_index += sector.free_sectors;
|
||||
write_count += sector.free_bytes as usize;
|
||||
}
|
||||
Ok(write_count)
|
||||
}
|
||||
335
block/src/formats/vhdx/internal/metadata.rs
Normal file
335
block/src/formats/vhdx/internal/metadata.rs
Normal file
@@ -0,0 +1,335 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::header::RegionTableEntry;
|
||||
|
||||
const METADATA_SIGN: u64 = 0x6174_6164_6174_656D;
|
||||
const METADATA_ENTRY_SIZE: usize = 32;
|
||||
const METADATA_MAX_ENTRIES: u16 = 2047;
|
||||
// The size including the table header and entries
|
||||
const METADATA_TABLE_MAX_SIZE: usize = METADATA_ENTRY_SIZE * (METADATA_MAX_ENTRIES as usize + 1);
|
||||
|
||||
const METADATA_FLAGS_IS_REQUIRED: u32 = 0x04;
|
||||
|
||||
pub const BLOCK_SIZE_MIN: u32 = 1 << 20; // 1 MiB
|
||||
const BLOCK_SIZE_MAX: u32 = 256 << 20; // 256 MiB
|
||||
const MAX_SECTORS_PER_BLOCK: u64 = 1 << 23;
|
||||
|
||||
const BLOCK_HAS_PARENT: u32 = 0x02; // Has a parent or a backing file
|
||||
|
||||
// GUID for known metadata items
|
||||
const METADATA_FILE_PARAMETER: &str = "CAA16737-FA36-4D43-B3B6-33F0AA44E76B";
|
||||
const METADATA_VIRTUAL_DISK_SIZE: &str = "2FA54224-CD1B-4876-B211-5DBED83BF4B8";
|
||||
const METADATA_VIRTUAL_DISK_ID: &str = "BECA12AB-B2E6-4523-93EF-C309E000C746";
|
||||
const METADATA_LOGICAL_SECTOR_SIZE: &str = "8141BF1D-A96F-4709-BA47-F233A8FAAB5F";
|
||||
const METADATA_PHYSICAL_SECTOR_SIZE: &str = "CDA348C7-445D-4471-9CC9-E9885251C556";
|
||||
const METADATA_PARENT_LOCATOR: &str = "A8D35F2D-B30B-454D-ABF7-D3D84834AB0C";
|
||||
|
||||
const METADATA_FILE_PARAMETER_PRESENT: u16 = 0x01;
|
||||
const METADATA_VIRTUAL_DISK_SIZE_PRESENT: u16 = 0x02;
|
||||
const METADATA_VIRTUAL_DISK_ID_PRESENT: u16 = 0x04;
|
||||
const METADATA_LOGICAL_SECTOR_SIZE_PRESENT: u16 = 0x08;
|
||||
const METADATA_PHYSICAL_SECTOR_SIZE_PRESENT: u16 = 0x10;
|
||||
const METADATA_PARENT_LOCATOR_PRESENT: u16 = 0x20;
|
||||
|
||||
const METADATA_ALL_PRESENT: u16 = METADATA_FILE_PARAMETER_PRESENT
|
||||
| METADATA_VIRTUAL_DISK_SIZE_PRESENT
|
||||
| METADATA_VIRTUAL_DISK_ID_PRESENT
|
||||
| METADATA_LOGICAL_SECTOR_SIZE_PRESENT
|
||||
| METADATA_PHYSICAL_SECTOR_SIZE_PRESENT;
|
||||
|
||||
const METADATA_LENGTH_MAX: u32 = 1 << 20; // 1 MiB
|
||||
|
||||
#[sorted]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum VhdxMetadataError {
|
||||
#[error("Invalid block size count")]
|
||||
InvalidBlockSize,
|
||||
#[error("Invalid disk size {0}")]
|
||||
InvalidDiskSize(u64),
|
||||
#[error("Invalid metadata entry count")]
|
||||
InvalidEntryCount,
|
||||
#[error("Invalid logical sector size")]
|
||||
InvalidLogicalSectorSize,
|
||||
#[error("Invalid metadata ID")]
|
||||
InvalidMetadataItem,
|
||||
#[error("Invalid metadata length")]
|
||||
InvalidMetadataLength,
|
||||
#[error("Metadata sign doesn't match")]
|
||||
InvalidMetadataSign,
|
||||
#[error("Invalid physical sector size")]
|
||||
InvalidPhysicalSectorSize,
|
||||
#[error("Invalid UUID")]
|
||||
InvalidUuid(#[source] uuid::Error),
|
||||
#[error("Invalid value")]
|
||||
InvalidValue,
|
||||
#[error("Not all required metadata found")]
|
||||
MissingMetadata,
|
||||
#[error("Failed to read metadata headers {0}")]
|
||||
ReadMetadata(#[source] io::Error),
|
||||
#[error("Reserved region has non-zero value")]
|
||||
ReservedIsNonZero,
|
||||
#[error("This implementation doesn't support this metadata flag")]
|
||||
UnsupportedFlag,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, VhdxMetadataError>;
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct DiskSpec {
|
||||
pub disk_id: u128,
|
||||
pub image_size: u64,
|
||||
pub block_size: u32,
|
||||
pub has_parent: bool,
|
||||
pub sectors_per_block: u32,
|
||||
pub virtual_disk_size: u64,
|
||||
pub logical_sector_size: u32,
|
||||
pub physical_sector_size: u32,
|
||||
pub chunk_ratio: u64,
|
||||
pub total_sectors: u64,
|
||||
}
|
||||
|
||||
impl DiskSpec {
|
||||
/// Parse all metadata from the provided file and store info in DiskSpec
|
||||
/// structure.
|
||||
pub fn new(f: &mut File, metadata_region: &RegionTableEntry) -> Result<DiskSpec> {
|
||||
let mut disk_spec = DiskSpec::default();
|
||||
let mut metadata_presence: u16 = 0;
|
||||
let mut offset = 0;
|
||||
let metadata = f.metadata().map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
disk_spec.image_size = metadata.len();
|
||||
|
||||
let mut buffer = [0u8; METADATA_TABLE_MAX_SIZE];
|
||||
f.seek(SeekFrom::Start(metadata_region.file_offset))
|
||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
f.read_exact(&mut buffer)
|
||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
|
||||
let metadata_header =
|
||||
MetadataTableHeader::new(&buffer[0..size_of::<MetadataTableHeader>()])?;
|
||||
|
||||
offset += size_of::<MetadataTableHeader>();
|
||||
for _ in 0..metadata_header.entry_count {
|
||||
let metadata_entry =
|
||||
MetadataTableEntry::new(&buffer[offset..offset + size_of::<MetadataTableEntry>()])?;
|
||||
|
||||
f.seek(SeekFrom::Start(
|
||||
metadata_region.file_offset + metadata_entry.offset as u64,
|
||||
))
|
||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
|
||||
if metadata_entry.item_id
|
||||
== Uuid::parse_str(METADATA_FILE_PARAMETER)
|
||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||
{
|
||||
disk_spec.block_size = f
|
||||
.read_u32::<LittleEndian>()
|
||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
|
||||
// MUST be at least 1 MiB and not greater than 256 MiB
|
||||
if disk_spec.block_size < BLOCK_SIZE_MIN || disk_spec.block_size > BLOCK_SIZE_MAX {
|
||||
return Err(VhdxMetadataError::InvalidBlockSize);
|
||||
}
|
||||
|
||||
// MUST be power of 2
|
||||
if !disk_spec.block_size.is_power_of_two() {
|
||||
return Err(VhdxMetadataError::InvalidBlockSize);
|
||||
}
|
||||
|
||||
let bits = f
|
||||
.read_u32::<LittleEndian>()
|
||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
disk_spec.has_parent = bits & BLOCK_HAS_PARENT != 0;
|
||||
|
||||
metadata_presence |= METADATA_FILE_PARAMETER_PRESENT;
|
||||
} else if metadata_entry.item_id
|
||||
== Uuid::parse_str(METADATA_VIRTUAL_DISK_SIZE)
|
||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||
{
|
||||
disk_spec.virtual_disk_size = f
|
||||
.read_u64::<LittleEndian>()
|
||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
|
||||
metadata_presence |= METADATA_VIRTUAL_DISK_SIZE_PRESENT;
|
||||
} else if metadata_entry.item_id
|
||||
== Uuid::parse_str(METADATA_VIRTUAL_DISK_ID)
|
||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||
{
|
||||
disk_spec.disk_id = f
|
||||
.read_u128::<LittleEndian>()
|
||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
|
||||
metadata_presence |= METADATA_VIRTUAL_DISK_ID_PRESENT;
|
||||
} else if metadata_entry.item_id
|
||||
== Uuid::parse_str(METADATA_LOGICAL_SECTOR_SIZE)
|
||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||
{
|
||||
disk_spec.logical_sector_size = f
|
||||
.read_u32::<LittleEndian>()
|
||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
if !(disk_spec.logical_sector_size == 512 || disk_spec.logical_sector_size == 4096)
|
||||
{
|
||||
return Err(VhdxMetadataError::InvalidLogicalSectorSize);
|
||||
}
|
||||
|
||||
metadata_presence |= METADATA_LOGICAL_SECTOR_SIZE_PRESENT;
|
||||
} else if metadata_entry.item_id
|
||||
== Uuid::parse_str(METADATA_PHYSICAL_SECTOR_SIZE)
|
||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||
{
|
||||
disk_spec.physical_sector_size = f
|
||||
.read_u32::<LittleEndian>()
|
||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||
if !(disk_spec.physical_sector_size == 512
|
||||
|| disk_spec.physical_sector_size == 4096)
|
||||
{
|
||||
return Err(VhdxMetadataError::InvalidPhysicalSectorSize);
|
||||
}
|
||||
|
||||
metadata_presence |= METADATA_PHYSICAL_SECTOR_SIZE_PRESENT;
|
||||
} else if metadata_entry.item_id
|
||||
== Uuid::parse_str(METADATA_PARENT_LOCATOR)
|
||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||
{
|
||||
metadata_presence |= METADATA_PARENT_LOCATOR_PRESENT;
|
||||
} else {
|
||||
return Err(VhdxMetadataError::InvalidMetadataItem);
|
||||
}
|
||||
|
||||
if (metadata_entry.flag_bits & METADATA_FLAGS_IS_REQUIRED) == 0 {
|
||||
return Err(VhdxMetadataError::UnsupportedFlag);
|
||||
}
|
||||
offset += size_of::<MetadataTableEntry>();
|
||||
}
|
||||
|
||||
// Check if all required metadata are present
|
||||
if metadata_presence != METADATA_ALL_PRESENT {
|
||||
return Err(VhdxMetadataError::MissingMetadata);
|
||||
}
|
||||
// Make sure virtual disk size is not zero
|
||||
if (metadata_presence & METADATA_VIRTUAL_DISK_SIZE_PRESENT != 0)
|
||||
&& disk_spec.virtual_disk_size == 0
|
||||
{
|
||||
return Err(VhdxMetadataError::InvalidDiskSize(
|
||||
disk_spec.virtual_disk_size,
|
||||
));
|
||||
}
|
||||
// Check if the virtual disk size is a multiple of the logical sector
|
||||
// size.
|
||||
if ((metadata_presence & METADATA_LOGICAL_SECTOR_SIZE_PRESENT) != 0)
|
||||
&& (disk_spec.virtual_disk_size % disk_spec.logical_sector_size as u64 != 0)
|
||||
{
|
||||
return Err(VhdxMetadataError::InvalidBlockSize);
|
||||
}
|
||||
|
||||
disk_spec.sectors_per_block =
|
||||
DiskSpec::sectors_per_block(disk_spec.block_size, disk_spec.logical_sector_size)?;
|
||||
|
||||
disk_spec.chunk_ratio =
|
||||
DiskSpec::chunk_ratio(disk_spec.block_size, disk_spec.logical_sector_size)?;
|
||||
|
||||
disk_spec.total_sectors =
|
||||
disk_spec.virtual_disk_size / disk_spec.logical_sector_size as u64;
|
||||
|
||||
Ok(disk_spec)
|
||||
}
|
||||
|
||||
/// Calculates the number of sectors per block
|
||||
fn sectors_per_block(block_size: u32, logical_sector_size: u32) -> Result<u32> {
|
||||
let sectors_per_block = block_size / logical_sector_size;
|
||||
|
||||
if !sectors_per_block.is_power_of_two() {
|
||||
return Err(VhdxMetadataError::InvalidValue);
|
||||
}
|
||||
|
||||
Ok(sectors_per_block)
|
||||
}
|
||||
|
||||
/// Calculate the chunk ratio
|
||||
fn chunk_ratio(block_size: u32, logical_sector_size: u32) -> Result<u64> {
|
||||
let chunk_ratio = (MAX_SECTORS_PER_BLOCK * logical_sector_size as u64) / block_size as u64;
|
||||
|
||||
if !chunk_ratio.is_power_of_two() {
|
||||
return Err(VhdxMetadataError::InvalidValue);
|
||||
}
|
||||
|
||||
Ok(chunk_ratio)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
struct MetadataTableHeader {
|
||||
signature: u64,
|
||||
reserved: u16,
|
||||
entry_count: u16,
|
||||
_reserved2: [u8; 20],
|
||||
}
|
||||
|
||||
impl MetadataTableHeader {
|
||||
pub fn new(buffer: &[u8]) -> Result<MetadataTableHeader> {
|
||||
assert!(buffer.len() == std::mem::size_of::<MetadataTableHeader>());
|
||||
// SAFETY: the assertion above makes sure the buffer size is correct.
|
||||
let metadata_table_header: MetadataTableHeader = unsafe { *(buffer.as_ptr().cast()) };
|
||||
|
||||
if metadata_table_header.signature != METADATA_SIGN {
|
||||
return Err(VhdxMetadataError::InvalidMetadataSign);
|
||||
}
|
||||
|
||||
if metadata_table_header.entry_count > METADATA_MAX_ENTRIES {
|
||||
return Err(VhdxMetadataError::InvalidEntryCount);
|
||||
}
|
||||
|
||||
if metadata_table_header.reserved != 0 {
|
||||
return Err(VhdxMetadataError::ReservedIsNonZero);
|
||||
}
|
||||
|
||||
Ok(metadata_table_header)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct MetadataTableEntry {
|
||||
item_id: Uuid,
|
||||
offset: u32,
|
||||
length: u32,
|
||||
flag_bits: u32,
|
||||
reserved: u32,
|
||||
}
|
||||
|
||||
impl MetadataTableEntry {
|
||||
/// Parse one metadata entry from the buffer
|
||||
fn new(buffer: &[u8]) -> Result<MetadataTableEntry> {
|
||||
assert!(buffer.len() == std::mem::size_of::<MetadataTableEntry>());
|
||||
// SAFETY: the assertion above makes sure the buffer size is correct.
|
||||
let mut metadata_table_entry: MetadataTableEntry = unsafe { *(buffer.as_ptr().cast()) };
|
||||
|
||||
let uuid = super::uuid_from_guid(buffer);
|
||||
metadata_table_entry.item_id = uuid;
|
||||
|
||||
if metadata_table_entry.length > METADATA_LENGTH_MAX {
|
||||
return Err(VhdxMetadataError::InvalidMetadataLength);
|
||||
}
|
||||
|
||||
if metadata_table_entry.length == 0 && metadata_table_entry.offset != 0 {
|
||||
return Err(VhdxMetadataError::InvalidMetadataLength);
|
||||
}
|
||||
|
||||
if metadata_table_entry.reserved != 0 {
|
||||
return Err(VhdxMetadataError::ReservedIsNonZero);
|
||||
}
|
||||
|
||||
Ok(metadata_table_entry)
|
||||
}
|
||||
}
|
||||
249
block/src/formats/vhdx/internal/mod.rs
Normal file
249
block/src/formats/vhdx/internal/mod.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::fd::{AsRawFd, RawFd};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use self::bat::{BatEntry, VhdxBatError};
|
||||
use self::header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
|
||||
use self::io::VhdxIoError;
|
||||
use self::metadata::{DiskSpec, VhdxMetadataError};
|
||||
use crate::BlockBackend;
|
||||
|
||||
mod bat;
|
||||
mod header;
|
||||
mod io;
|
||||
mod metadata;
|
||||
|
||||
#[sorted]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum VhdxError {
|
||||
#[error("Not a VHDx file")]
|
||||
NotVhdx(#[source] VhdxHeaderError),
|
||||
#[error("Failed to parse VHDx header")]
|
||||
ParseVhdxHeader(#[source] VhdxHeaderError),
|
||||
#[error("Failed to parse VHDx metadata")]
|
||||
ParseVhdxMetadata(#[source] VhdxMetadataError),
|
||||
#[error("Failed to parse VHDx region entries")]
|
||||
ParseVhdxRegionEntry(#[source] VhdxHeaderError),
|
||||
#[error("Failed reading metadata")]
|
||||
ReadBatEntry(#[source] VhdxBatError),
|
||||
#[error("Failed reading sector from disk")]
|
||||
ReadFailed(#[source] VhdxIoError),
|
||||
#[error("Failed writing to sector on disk")]
|
||||
WriteFailed(#[source] VhdxIoError),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, VhdxError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Vhdx {
|
||||
file: File,
|
||||
vhdx_header: VhdxHeader,
|
||||
region_entries: BTreeMap<u64, u64>,
|
||||
bat_entry: RegionTableEntry,
|
||||
mdr_entry: RegionTableEntry,
|
||||
disk_spec: DiskSpec,
|
||||
bat_entries: Vec<BatEntry>,
|
||||
current_offset: u64,
|
||||
first_write: bool,
|
||||
}
|
||||
|
||||
impl Vhdx {
|
||||
/// Parse the Vhdx header, BAT, and metadata from a file and store info
|
||||
// in Vhdx structure.
|
||||
pub fn new(mut file: File) -> Result<Vhdx> {
|
||||
let vhdx_header = VhdxHeader::new(&mut file).map_err(VhdxError::ParseVhdxHeader)?;
|
||||
|
||||
let collected_entries = RegionInfo::new(
|
||||
&mut file,
|
||||
header::REGION_TABLE_1_START,
|
||||
vhdx_header.region_entry_count(),
|
||||
)
|
||||
.map_err(VhdxError::ParseVhdxRegionEntry)?;
|
||||
|
||||
let bat_entry = collected_entries.bat_entry;
|
||||
let mdr_entry = collected_entries.mdr_entry;
|
||||
|
||||
let disk_spec =
|
||||
DiskSpec::new(&mut file, &mdr_entry).map_err(VhdxError::ParseVhdxMetadata)?;
|
||||
let bat_entries = BatEntry::collect_bat_entries(&mut file, &disk_spec, &bat_entry)
|
||||
.map_err(VhdxError::ReadBatEntry)?;
|
||||
|
||||
Ok(Vhdx {
|
||||
file,
|
||||
vhdx_header,
|
||||
region_entries: collected_entries.region_entries,
|
||||
bat_entry,
|
||||
mdr_entry,
|
||||
disk_spec,
|
||||
bat_entries,
|
||||
current_offset: 0,
|
||||
first_write: true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn virtual_disk_size(&self) -> u64 {
|
||||
self.disk_spec.virtual_disk_size
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Vhdx {
|
||||
/// Wrapper function to satisfy Read trait implementation for VHDx disk.
|
||||
/// Convert the offset to sector index and buffer length to sector count.
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> {
|
||||
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
|
||||
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
|
||||
|
||||
let result = io::read(
|
||||
&mut self.file,
|
||||
buf,
|
||||
&self.disk_spec,
|
||||
&self.bat_entries,
|
||||
sector_index,
|
||||
sector_count,
|
||||
)
|
||||
.map_err(|e| {
|
||||
std::io::Error::other(format!(
|
||||
"Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Vhdx {
|
||||
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
|
||||
self.file.flush()
|
||||
}
|
||||
|
||||
/// Wrapper function to satisfy Write trait implementation for VHDx disk.
|
||||
/// Convert the offset to sector index and buffer length to sector count.
|
||||
fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, std::io::Error> {
|
||||
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
|
||||
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
|
||||
|
||||
if self.first_write {
|
||||
self.first_write = false;
|
||||
self.vhdx_header
|
||||
.update(&mut self.file)
|
||||
.map_err(|e| std::io::Error::other(format!("Failed to update VHDx header: {e}")))?;
|
||||
}
|
||||
|
||||
let result = io::write(
|
||||
&mut self.file,
|
||||
buf,
|
||||
&mut self.disk_spec,
|
||||
self.bat_entry.file_offset,
|
||||
&mut self.bat_entries,
|
||||
sector_index,
|
||||
sector_count,
|
||||
)
|
||||
.map_err(|e| {
|
||||
std::io::Error::other(format!(
|
||||
"Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for Vhdx {
|
||||
/// Wrapper function to satisfy Seek trait implementation for VHDx disk.
|
||||
/// Updates the offset field in the Vhdx struct.
|
||||
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||
let new_offset: Option<u64> = match pos {
|
||||
SeekFrom::Start(off) => Some(off),
|
||||
SeekFrom::End(off) => {
|
||||
if off < 0 {
|
||||
0i64.checked_sub(off).and_then(|increment| {
|
||||
self.virtual_disk_size().checked_sub(increment as u64)
|
||||
})
|
||||
} else {
|
||||
self.virtual_disk_size().checked_add(off as u64)
|
||||
}
|
||||
}
|
||||
SeekFrom::Current(off) => {
|
||||
if off < 0 {
|
||||
0i64.checked_sub(off)
|
||||
.and_then(|increment| self.current_offset.checked_sub(increment as u64))
|
||||
} else {
|
||||
self.current_offset.checked_add(off as u64)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(o) = new_offset
|
||||
&& o <= self.virtual_disk_size()
|
||||
{
|
||||
self.current_offset = o;
|
||||
return Ok(o);
|
||||
}
|
||||
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Failed seek operation",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockBackend for Vhdx {
|
||||
fn logical_size(&self) -> std::result::Result<u64, crate::Error> {
|
||||
Ok(self.virtual_disk_size())
|
||||
}
|
||||
|
||||
fn physical_size(&self) -> std::result::Result<u64, crate::Error> {
|
||||
self.file
|
||||
.metadata()
|
||||
.map(|m| m.len())
|
||||
.map_err(crate::Error::GetFileMetadata)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Vhdx {
|
||||
fn clone(&self) -> Self {
|
||||
Vhdx {
|
||||
file: self.file.try_clone().unwrap(),
|
||||
vhdx_header: self.vhdx_header.clone(),
|
||||
region_entries: self.region_entries.clone(),
|
||||
bat_entry: self.bat_entry,
|
||||
mdr_entry: self.mdr_entry,
|
||||
disk_spec: self.disk_spec.clone(),
|
||||
bat_entries: self.bat_entries.clone(),
|
||||
current_offset: self.current_offset,
|
||||
first_write: self.first_write,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Vhdx {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.file.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn uuid_from_guid(buf: &[u8]) -> Uuid {
|
||||
// The first 3 fields of UUID are stored in Big Endian format, and
|
||||
// the last 8 bytes are stored as byte array. Therefore, we read the
|
||||
// first 3 fields in Big Endian format instead of Little Endian.
|
||||
Uuid::from_fields_le(
|
||||
BigEndian::read_u32(&buf[0..4]),
|
||||
BigEndian::read_u16(&buf[4..6]),
|
||||
BigEndian::read_u16(&buf[6..8]),
|
||||
buf[8..16].try_into().unwrap(),
|
||||
)
|
||||
}
|
||||
111
block/src/formats/vhdx/mod.rs
Normal file
111
block/src/formats/vhdx/mod.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! VHDX disk format support.
|
||||
//!
|
||||
//! Provides [`VhdxDisk`], the `DiskFile` wrapper for dynamic VHDX
|
||||
//! images.
|
||||
|
||||
pub mod internal;
|
||||
pub(crate) mod worker;
|
||||
|
||||
use std::fs::File;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub use internal::VhdxError;
|
||||
|
||||
use self::internal::Vhdx;
|
||||
use self::worker::sync::VhdxSync;
|
||||
use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError};
|
||||
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
|
||||
use crate::{BlockBackend, Error, disk_file};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VhdxDisk {
|
||||
// FIXME: The Mutex serializes all VHDX I/O operations across queues, which
|
||||
// is necessary for correctness but eliminates any parallelism benefit from
|
||||
// multiqueue. Vhdx::clone() shares the underlying file description across
|
||||
// threads, so concurrent I/O from multiple queues races on the file offset
|
||||
// causing data corruption.
|
||||
//
|
||||
// A proper fix would require restructuring the VHDX I/O path so that data
|
||||
// operations can proceed in parallel with independent file descriptors.
|
||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||
}
|
||||
|
||||
impl VhdxDisk {
|
||||
pub fn new(f: File) -> BlockResult<Self> {
|
||||
Ok(VhdxDisk {
|
||||
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f).map_err(|e| {
|
||||
let kind = match &e {
|
||||
VhdxError::NotVhdx(_)
|
||||
| VhdxError::ParseVhdxHeader(_)
|
||||
| VhdxError::ParseVhdxMetadata(_)
|
||||
| VhdxError::ParseVhdxRegionEntry(_) => BlockErrorKind::InvalidFormat,
|
||||
VhdxError::ReadBatEntry(_) => BlockErrorKind::CorruptImage,
|
||||
VhdxError::ReadFailed(_) | VhdxError::WriteFailed(_) => BlockErrorKind::Io,
|
||||
};
|
||||
BlockError::new(kind, e).with_op(ErrorOp::Open)
|
||||
})?)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl disk_file::DiskSize for VhdxDisk {
|
||||
fn logical_size(&self) -> BlockResult<u64> {
|
||||
Ok(self.vhdx_file.lock().unwrap().virtual_disk_size())
|
||||
}
|
||||
}
|
||||
|
||||
impl disk_file::PhysicalSize for VhdxDisk {
|
||||
fn physical_size(&self) -> BlockResult<u64> {
|
||||
self.vhdx_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.physical_size()
|
||||
.map_err(|e| match e {
|
||||
Error::GetFileMetadata(io) => {
|
||||
BlockError::new(BlockErrorKind::Io, Error::GetFileMetadata(io))
|
||||
}
|
||||
_ => unreachable!("unexpected error from Vhdx::physical_size(): {e}"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl disk_file::DiskFd for VhdxDisk {
|
||||
fn fd(&self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.vhdx_file.lock().unwrap().as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
impl disk_file::Geometry for VhdxDisk {}
|
||||
|
||||
impl disk_file::SparseCapable for VhdxDisk {}
|
||||
|
||||
impl disk_file::Resizable for VhdxDisk {
|
||||
fn resize(&mut self, _size: u64) -> BlockResult<()> {
|
||||
Err(BlockError::new(
|
||||
BlockErrorKind::UnsupportedFeature,
|
||||
DiskFileError::ResizeError(std::io::Error::other("resize not supported for VHDX")),
|
||||
)
|
||||
.with_op(ErrorOp::Resize))
|
||||
}
|
||||
}
|
||||
|
||||
impl disk_file::DiskFile for VhdxDisk {}
|
||||
|
||||
impl disk_file::AsyncDiskFile for VhdxDisk {
|
||||
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
|
||||
Ok(Box::new(VhdxDisk {
|
||||
vhdx_file: Arc::clone(&self.vhdx_file),
|
||||
}))
|
||||
}
|
||||
|
||||
fn create_async_io(&self, _ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(VhdxSync::new(Arc::clone(&self.vhdx_file))))
|
||||
}
|
||||
}
|
||||
7
block/src/formats/vhdx/worker/mod.rs
Normal file
7
block/src/formats/vhdx/worker/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Sync I/O worker for VHDX images.
|
||||
|
||||
pub(crate) mod sync;
|
||||
109
block/src/formats/vhdx/worker/sync.rs
Normal file
109
block/src/formats/vhdx/worker/sync.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
|
||||
use crate::formats::vhdx::internal::Vhdx;
|
||||
|
||||
pub struct VhdxSync {
|
||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||
eventfd: EventFd,
|
||||
completion_list: VecDeque<AsyncIoCompletion>,
|
||||
}
|
||||
|
||||
impl VhdxSync {
|
||||
pub fn new(vhdx_file: Arc<Mutex<Vhdx>>) -> Self {
|
||||
VhdxSync {
|
||||
vhdx_file,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||
.expect("Failed creating EventFd for VhdxSync"),
|
||||
completion_list: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_operation(&mut self, op: &mut AsyncIoOperation) -> AsyncIoResult<usize> {
|
||||
let offset = op.offset();
|
||||
let mut buf = vec![0u8; op.total_len()];
|
||||
let mut vhdx = self.vhdx_file.lock().unwrap();
|
||||
vhdx.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::ReadVectored)?;
|
||||
let result = vhdx.read(&mut buf).map_err(AsyncIoError::ReadVectored)?;
|
||||
drop(vhdx);
|
||||
|
||||
op.write_bytes_at(0, &buf[..result])
|
||||
.map_err(AsyncIoError::ReadVectored)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn write_operation(&mut self, op: &AsyncIoOperation) -> AsyncIoResult<usize> {
|
||||
let offset = op.offset();
|
||||
let mut buf = vec![0u8; op.total_len()];
|
||||
op.read_bytes_at(0, &mut buf)
|
||||
.map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
let mut vhdx = self.vhdx_file.lock().unwrap();
|
||||
vhdx.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::WriteVectored)?;
|
||||
let result = vhdx.write(&buf).map_err(AsyncIoError::WriteVectored)?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for VhdxSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
|
||||
let is_read = op.is_read();
|
||||
let mut op = op;
|
||||
let result = if is_read {
|
||||
self.read_operation(&mut op)?
|
||||
} else {
|
||||
self.write_operation(&op)?
|
||||
};
|
||||
|
||||
self.completion_list
|
||||
.push_back(AsyncIoCompletion::from_operation(op, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
self.vhdx_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.flush()
|
||||
.map_err(AsyncIoError::Fsync)?;
|
||||
if let Some(user_data) = user_data {
|
||||
self.completion_list
|
||||
.push_back(AsyncIoCompletion::new(user_data, 0, None));
|
||||
self.eventfd.write(1).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
|
||||
self.completion_list.pop_front()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::PunchHole(std::io::Error::other(
|
||||
"punch_hole not supported for VHDX",
|
||||
)))
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::WriteZeroes(std::io::Error::other(
|
||||
"write_zeroes not supported for VHDX",
|
||||
)))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user