mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
size_of is part of std::prelude as of Rust 1.80 (with size_of_val, align_of, align_of_val), and the workspace MSRV is 1.89, so qualifying it (mem::size_of, std::mem::size_of, core::mem::size_of) is unnecessary. Convert every qualified size_of call-site to the bare prelude form and drop the now-redundant `use std::mem::size_of;` imports, keeping `use std::mem;` where it still serves non-prelude items (transmute, swap, replace, take, zeroed, MaybeUninit, offset_of). size_of is the only one of the four currently used in the tree. Pure refactor, no behavioural change. Follow-up to the clippy::absolute_paths cleanup (#7670), as discussed in #8444. Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com> Assisted-by: Claude:Opus-4.8
455 lines
16 KiB
Rust
455 lines
16 KiB
Rust
// Copyright © 2021 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use std::collections::btree_map::BTreeMap;
|
|
use std::os::unix::fs::FileExt;
|
|
use std::{io, result, slice};
|
|
|
|
use byteorder::{ByteOrder, LittleEndian};
|
|
use remain::sorted;
|
|
use thiserror::Error;
|
|
use uuid::Uuid;
|
|
|
|
use crate::aligned_file::AlignedFile;
|
|
|
|
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] 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("We do not recognize this entry")]
|
|
UnrecognizedRegionEntry,
|
|
#[error("Failed to write header {0}")]
|
|
WriteHeader(#[source] io::Error),
|
|
}
|
|
|
|
pub type Result<T> = 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: &AlignedFile) -> Result<FileTypeIdentifier> {
|
|
let mut buf = [0u8; size_of::<u64>()];
|
|
f.read_exact_at(&mut buf, FILE_START)
|
|
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
|
|
let _signature = LittleEndian::read_u64(&buf);
|
|
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: &AlignedFile, 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.read_exact_at(&mut buffer, start)
|
|
.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 { 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: &AlignedFile,
|
|
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.write_all_at(&buffer, start)
|
|
.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: &AlignedFile, 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.read_exact_at(&mut buffer, start)
|
|
.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: &AlignedFile, 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];
|
|
// Read after the Region Table Header
|
|
f.read_exact_at(
|
|
&mut buffer,
|
|
region_start + size_of::<RegionTableHeader>() as u64,
|
|
)
|
|
.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() == 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: &AlignedFile) -> 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: &AlignedFile,
|
|
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: &AlignedFile,
|
|
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: &AlignedFile) -> 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
|
|
}
|