mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: vhdx: Use AlignedFile for O_DIRECT-safe I/O
By redirecting VHDx I/O through the AlignedFile the required RMW semantics can be achieved for writes less than the logical block size whilt reusing the same logic used for other backend implementations. Assisted-by: Claude:Opus-4.8 Signed-off-by: Rob Bradford <rbradford@meta.com>
This commit is contained in:
@@ -68,6 +68,13 @@ impl AlignedFile {
|
|||||||
alignment: self.alignment,
|
alignment: self.alignment,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrap `file` with an explicit alignment, bypassing the probe. Used by
|
||||||
|
/// tests to force the bounce/RMW path without a real O_DIRECT fd.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn with_alignment(file: File, alignment: usize) -> Self {
|
||||||
|
AlignedFile { file, alignment }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileExt for AlignedFile {
|
impl FileExt for AlignedFile {
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ fn open_vhdx(
|
|||||||
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
|
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
|
||||||
info!("Opening VHDX disk file with synchronous backend");
|
info!("Opening VHDX disk file with synchronous backend");
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
VhdxDisk::new(file).map_err(|e| e.with_path(options.path))?,
|
VhdxDisk::new(file, options.direct).map_err(|e| e.with_path(options.path))?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Seek, SeekFrom};
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::result;
|
use std::os::unix::fs::FileExt;
|
||||||
|
use std::{io, result};
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::header::RegionTableEntry;
|
use super::header::RegionTableEntry;
|
||||||
use super::metadata::DiskSpec;
|
use super::metadata::DiskSpec;
|
||||||
|
use crate::aligned_file::AlignedFile;
|
||||||
|
|
||||||
// Payload BAT Entry States
|
// Payload BAT Entry States
|
||||||
pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
|
pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
|
||||||
@@ -48,7 +48,7 @@ pub struct BatEntry(pub u64);
|
|||||||
impl BatEntry {
|
impl BatEntry {
|
||||||
// Read all BAT entries presented on the disk and insert them to a vector
|
// Read all BAT entries presented on the disk and insert them to a vector
|
||||||
pub fn collect_bat_entries(
|
pub fn collect_bat_entries(
|
||||||
f: &mut File,
|
f: &AlignedFile,
|
||||||
disk_spec: &DiskSpec,
|
disk_spec: &DiskSpec,
|
||||||
bat_entry: &RegionTableEntry,
|
bat_entry: &RegionTableEntry,
|
||||||
) -> Result<Vec<BatEntry>> {
|
) -> Result<Vec<BatEntry>> {
|
||||||
@@ -64,14 +64,10 @@ impl BatEntry {
|
|||||||
let mut bat: Vec<BatEntry> = Vec::with_capacity(bat_entry.length as usize);
|
let mut bat: Vec<BatEntry> = Vec::with_capacity(bat_entry.length as usize);
|
||||||
let offset = bat_entry.file_offset;
|
let offset = bat_entry.file_offset;
|
||||||
for i in 0..entry_count {
|
for i in 0..entry_count {
|
||||||
f.seek(SeekFrom::Start(offset + i * size_of::<u64>() as u64))
|
let mut entry = [0u8; size_of::<u64>()];
|
||||||
|
f.read_exact_at(&mut entry, offset + i * size_of::<u64>() as u64)
|
||||||
.map_err(VhdxBatError::ReadBat)?;
|
.map_err(VhdxBatError::ReadBat)?;
|
||||||
|
bat.insert(i as usize, BatEntry(LittleEndian::read_u64(&entry)));
|
||||||
let bat_entry = BatEntry(
|
|
||||||
f.read_u64::<LittleEndian>()
|
|
||||||
.map_err(VhdxBatError::ReadBat)?,
|
|
||||||
);
|
|
||||||
bat.insert(i as usize, bat_entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(bat)
|
Ok(bat)
|
||||||
@@ -85,13 +81,11 @@ impl BatEntry {
|
|||||||
|
|
||||||
// Routine for writing BAT entries to the disk
|
// Routine for writing BAT entries to the disk
|
||||||
pub fn write_bat_entries(
|
pub fn write_bat_entries(
|
||||||
f: &mut File,
|
f: &AlignedFile,
|
||||||
bat_offset: u64,
|
bat_offset: u64,
|
||||||
bat_entries: &[BatEntry],
|
bat_entries: &[BatEntry],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for i in 0..bat_entries.len() as u64 {
|
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) {
|
let bat_entry = match bat_entries.get(i as usize) {
|
||||||
Some(entry) => entry.0,
|
Some(entry) => entry.0,
|
||||||
None => {
|
None => {
|
||||||
@@ -99,7 +93,9 @@ impl BatEntry {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
f.write_u64::<LittleEndian>(bat_entry)
|
let mut buf = [0u8; size_of::<u64>()];
|
||||||
|
LittleEndian::write_u64(&mut buf, bat_entry);
|
||||||
|
f.write_all_at(&buf, bat_offset + i * size_of::<u64>() as u64)
|
||||||
.map_err(VhdxBatError::WriteBat)?;
|
.map_err(VhdxBatError::WriteBat)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -3,16 +3,17 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::collections::btree_map::BTreeMap;
|
use std::collections::btree_map::BTreeMap;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::{result, slice};
|
use std::os::unix::fs::FileExt;
|
||||||
|
use std::{io, result, slice};
|
||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::aligned_file::AlignedFile;
|
||||||
|
|
||||||
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876; // "vhdxfile"
|
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876; // "vhdxfile"
|
||||||
const HEADER_SIGN: u32 = 0x6461_6568; // "head"
|
const HEADER_SIGN: u32 = 0x6461_6568; // "head"
|
||||||
const REGION_SIGN: u32 = 0x6967_6572; // "regi"
|
const REGION_SIGN: u32 = 0x6967_6572; // "regi"
|
||||||
@@ -72,14 +73,6 @@ pub enum VhdxHeaderError {
|
|||||||
RegionOverlap,
|
RegionOverlap,
|
||||||
#[error("Reserved region has non-zero value")]
|
#[error("Reserved region has non-zero value")]
|
||||||
ReservedIsNonZero,
|
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")]
|
#[error("We do not recognize this entry")]
|
||||||
UnrecognizedRegionEntry,
|
UnrecognizedRegionEntry,
|
||||||
#[error("Failed to write header {0}")]
|
#[error("Failed to write header {0}")]
|
||||||
@@ -95,12 +88,11 @@ pub struct FileTypeIdentifier {
|
|||||||
|
|
||||||
impl FileTypeIdentifier {
|
impl FileTypeIdentifier {
|
||||||
/// Reads the File Type Identifier structure from a reference VHDx file
|
/// Reads the File Type Identifier structure from a reference VHDx file
|
||||||
pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
|
pub fn new(f: &AlignedFile) -> Result<FileTypeIdentifier> {
|
||||||
f.seek(SeekFrom::Start(FILE_START))
|
let mut buf = [0u8; size_of::<u64>()];
|
||||||
.map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
|
f.read_exact_at(&mut buf, FILE_START)
|
||||||
let _signature = f
|
|
||||||
.read_u64::<LittleEndian>()
|
|
||||||
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
|
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
|
||||||
|
let _signature = LittleEndian::read_u64(&buf);
|
||||||
if _signature != VHDX_SIGN {
|
if _signature != VHDX_SIGN {
|
||||||
return Err(VhdxHeaderError::InvalidVHDXSign);
|
return Err(VhdxHeaderError::InvalidVHDXSign);
|
||||||
}
|
}
|
||||||
@@ -126,13 +118,11 @@ pub struct Header {
|
|||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
/// Reads the Header structure from a reference VHDx file
|
/// Reads the Header structure from a reference VHDx file
|
||||||
pub fn new(f: &mut File, start: u64) -> Result<Header> {
|
pub fn new(f: &AlignedFile, start: u64) -> Result<Header> {
|
||||||
// Read the whole header into a buffer. We will need it for
|
// Read the whole header into a buffer. We will need it for
|
||||||
// calculating checksum.
|
// calculating checksum.
|
||||||
let mut buffer = [0; HEADER_SIZE as usize];
|
let mut buffer = [0; HEADER_SIZE as usize];
|
||||||
f.seek(SeekFrom::Start(start))
|
f.read_exact_at(&mut buffer, start)
|
||||||
.map_err(VhdxHeaderError::SeekHeader)?;
|
|
||||||
f.read_exact(&mut buffer)
|
|
||||||
.map_err(VhdxHeaderError::ReadHeader)?;
|
.map_err(VhdxHeaderError::ReadHeader)?;
|
||||||
|
|
||||||
// SAFETY: buffer is of correct size and has been successfully filled.
|
// SAFETY: buffer is of correct size and has been successfully filled.
|
||||||
@@ -159,7 +149,7 @@ impl Header {
|
|||||||
|
|
||||||
/// Creates and returns new updated header from the provided current header
|
/// Creates and returns new updated header from the provided current header
|
||||||
fn update_header(
|
fn update_header(
|
||||||
f: &mut File,
|
f: &AlignedFile,
|
||||||
current_header: &Header,
|
current_header: &Header,
|
||||||
change_data_guid: bool,
|
change_data_guid: bool,
|
||||||
mut file_write_guid: u128,
|
mut file_write_guid: u128,
|
||||||
@@ -193,9 +183,8 @@ impl Header {
|
|||||||
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||||
new_header.write_to_buffer(&mut buffer);
|
new_header.write_to_buffer(&mut buffer);
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(start))
|
f.write_all_at(&buffer, start)
|
||||||
.map_err(VhdxHeaderError::SeekHeader)?;
|
.map_err(VhdxHeaderError::WriteHeader)?;
|
||||||
f.write(&buffer).map_err(VhdxHeaderError::WriteHeader)?;
|
|
||||||
|
|
||||||
Ok(new_header)
|
Ok(new_header)
|
||||||
}
|
}
|
||||||
@@ -212,13 +201,11 @@ struct RegionTableHeader {
|
|||||||
|
|
||||||
impl RegionTableHeader {
|
impl RegionTableHeader {
|
||||||
/// Reads the Region Table Header structure from a reference VHDx file
|
/// Reads the Region Table Header structure from a reference VHDx file
|
||||||
pub fn new(f: &mut File, start: u64) -> Result<RegionTableHeader> {
|
pub fn new(f: &AlignedFile, start: u64) -> Result<RegionTableHeader> {
|
||||||
// Read the whole header into a buffer. We will need it for calculating
|
// Read the whole header into a buffer. We will need it for calculating
|
||||||
// checksum.
|
// checksum.
|
||||||
let mut buffer = [0u8; REGION_SIZE as usize];
|
let mut buffer = [0u8; REGION_SIZE as usize];
|
||||||
f.seek(SeekFrom::Start(start))
|
f.read_exact_at(&mut buffer, start)
|
||||||
.map_err(VhdxHeaderError::SeekRegionTableHeader)?;
|
|
||||||
f.read_exact(&mut buffer)
|
|
||||||
.map_err(VhdxHeaderError::ReadRegionTableHeader)?;
|
.map_err(VhdxHeaderError::ReadRegionTableHeader)?;
|
||||||
|
|
||||||
// SAFETY: buffer is of correct size and has been successfully filled.
|
// SAFETY: buffer is of correct size and has been successfully filled.
|
||||||
@@ -253,7 +240,7 @@ pub struct RegionInfo {
|
|||||||
impl RegionInfo {
|
impl RegionInfo {
|
||||||
/// Collect all entries in a BTreeMap from the Region Table and identifies
|
/// Collect all entries in a BTreeMap from the Region Table and identifies
|
||||||
/// BAT and metadata regions
|
/// BAT and metadata regions
|
||||||
pub fn new(f: &mut File, region_start: u64, entry_count: u32) -> Result<RegionInfo> {
|
pub fn new(f: &AlignedFile, region_start: u64, entry_count: u32) -> Result<RegionInfo> {
|
||||||
let mut bat_entry: Option<RegionTableEntry> = None;
|
let mut bat_entry: Option<RegionTableEntry> = None;
|
||||||
let mut mdr_entry: Option<RegionTableEntry> = None;
|
let mut mdr_entry: Option<RegionTableEntry> = None;
|
||||||
|
|
||||||
@@ -261,13 +248,12 @@ impl RegionInfo {
|
|||||||
let mut region_entries = BTreeMap::new();
|
let mut region_entries = BTreeMap::new();
|
||||||
|
|
||||||
let mut buffer = [0; REGION_SIZE as usize];
|
let mut buffer = [0; REGION_SIZE as usize];
|
||||||
// Seek after the Region Table Header
|
// Read after the Region Table Header
|
||||||
f.seek(SeekFrom::Start(
|
f.read_exact_at(
|
||||||
|
&mut buffer,
|
||||||
region_start + size_of::<RegionTableHeader>() as u64,
|
region_start + size_of::<RegionTableHeader>() as u64,
|
||||||
))
|
)
|
||||||
.map_err(VhdxHeaderError::SeekRegionTableEntries)?;
|
.map_err(VhdxHeaderError::ReadRegionTableEntries)?;
|
||||||
f.read_exact(&mut buffer)
|
|
||||||
.map_err(VhdxHeaderError::ReadRegionTableEntries)?;
|
|
||||||
|
|
||||||
for _ in 0..entry_count {
|
for _ in 0..entry_count {
|
||||||
let entry =
|
let entry =
|
||||||
@@ -366,7 +352,7 @@ pub struct VhdxHeader {
|
|||||||
|
|
||||||
impl VhdxHeader {
|
impl VhdxHeader {
|
||||||
/// Creates a VhdxHeader from a reference to a file
|
/// Creates a VhdxHeader from a reference to a file
|
||||||
pub fn new(f: &mut File) -> Result<VhdxHeader> {
|
pub fn new(f: &AlignedFile) -> Result<VhdxHeader> {
|
||||||
Ok(VhdxHeader {
|
Ok(VhdxHeader {
|
||||||
_file_type_identifier: FileTypeIdentifier::new(f)?,
|
_file_type_identifier: FileTypeIdentifier::new(f)?,
|
||||||
header_1: Header::new(f, HEADER_1_START)?,
|
header_1: Header::new(f, HEADER_1_START)?,
|
||||||
@@ -403,7 +389,7 @@ impl VhdxHeader {
|
|||||||
/// current one. Returns both headers as a tuple sequenced the way it was
|
/// current one. Returns both headers as a tuple sequenced the way it was
|
||||||
/// received from the parameter list.
|
/// received from the parameter list.
|
||||||
fn update_header(
|
fn update_header(
|
||||||
f: &mut File,
|
f: &AlignedFile,
|
||||||
header_1: Result<Header>,
|
header_1: Result<Header>,
|
||||||
header_2: Result<Header>,
|
header_2: Result<Header>,
|
||||||
guid: u128,
|
guid: u128,
|
||||||
@@ -426,7 +412,7 @@ impl VhdxHeader {
|
|||||||
|
|
||||||
// Update the provided headers according to the spec
|
// Update the provided headers according to the spec
|
||||||
fn update_headers(
|
fn update_headers(
|
||||||
f: &mut File,
|
f: &AlignedFile,
|
||||||
header_1: Result<Header>,
|
header_1: Result<Header>,
|
||||||
header_2: Result<Header>,
|
header_2: Result<Header>,
|
||||||
guid: u128,
|
guid: u128,
|
||||||
@@ -436,7 +422,7 @@ impl VhdxHeader {
|
|||||||
VhdxHeader::update_header(f, Ok(header_1), Ok(header_2), guid)
|
VhdxHeader::update_header(f, Ok(header_1), Ok(header_2), guid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, f: &mut File) -> Result<()> {
|
pub fn update(&mut self, f: &AlignedFile) -> Result<()> {
|
||||||
let headers = VhdxHeader::update_headers(f, Ok(self.header_1), Ok(self.header_2), 0)?;
|
let headers = VhdxHeader::update_headers(f, Ok(self.header_1), Ok(self.header_2), 0)?;
|
||||||
self.header_1 = headers.0;
|
self.header_1 = headers.0;
|
||||||
self.header_2 = headers.1;
|
self.header_2 = headers.1;
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::fs::File;
|
use std::os::unix::fs::FileExt;
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::{io, result};
|
||||||
use std::result;
|
|
||||||
|
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::bat::{self, BatEntry, VhdxBatError};
|
use super::bat::{self, BatEntry, VhdxBatError};
|
||||||
use super::metadata::{self, DiskSpec};
|
use super::metadata::{self, DiskSpec};
|
||||||
|
use crate::aligned_file::AlignedFile;
|
||||||
|
|
||||||
const SECTOR_SIZE: u64 = 512;
|
const SECTOR_SIZE: u64 = 512;
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ impl Sector {
|
|||||||
/// VHDx IO read routine: requires relative sector index and count for the
|
/// VHDx IO read routine: requires relative sector index and count for the
|
||||||
/// requested data.
|
/// requested data.
|
||||||
pub fn read(
|
pub fn read(
|
||||||
f: &mut File,
|
f: &AlignedFile,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
disk_spec: &DiskSpec,
|
disk_spec: &DiskSpec,
|
||||||
bat: &[BatEntry],
|
bat: &[BatEntry],
|
||||||
@@ -115,11 +115,10 @@ pub fn read(
|
|||||||
| bat::PAYLOAD_BLOCK_UNMAPPED
|
| bat::PAYLOAD_BLOCK_UNMAPPED
|
||||||
| bat::PAYLOAD_BLOCK_ZERO => {}
|
| bat::PAYLOAD_BLOCK_ZERO => {}
|
||||||
bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
f.read_exact_at(
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.read_exact(
|
|
||||||
&mut buf
|
&mut buf
|
||||||
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||||
|
sector.file_offset,
|
||||||
)
|
)
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||||
}
|
}
|
||||||
@@ -140,7 +139,7 @@ pub fn read(
|
|||||||
/// VHDx IO write routine: requires relative sector index and count for the
|
/// VHDx IO write routine: requires relative sector index and count for the
|
||||||
/// requested data.
|
/// requested data.
|
||||||
pub fn write(
|
pub fn write(
|
||||||
f: &mut File,
|
f: &AlignedFile,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
disk_spec: &mut DiskSpec,
|
disk_spec: &mut DiskSpec,
|
||||||
bat_offset: u64,
|
bat_offset: u64,
|
||||||
@@ -173,7 +172,9 @@ pub fn write(
|
|||||||
.checked_add(disk_spec.block_size as u64)
|
.checked_add(disk_spec.block_size as u64)
|
||||||
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
||||||
|
|
||||||
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
|
f.file()
|
||||||
|
.set_len(new_size)
|
||||||
|
.map_err(VhdxIoError::ResizeFile)?;
|
||||||
disk_spec.image_size = new_size;
|
disk_spec.image_size = new_size;
|
||||||
|
|
||||||
let new_bat_entry =
|
let new_bat_entry =
|
||||||
@@ -185,10 +186,9 @@ pub fn write(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(file_offset))
|
f.write_all_at(
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||||
|
file_offset,
|
||||||
)
|
)
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||||
}
|
}
|
||||||
@@ -197,10 +197,9 @@ pub fn write(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
f.write_all_at(
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||||
|
sector.file_offset,
|
||||||
)
|
)
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom};
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::result;
|
use std::os::unix::fs::FileExt;
|
||||||
|
use std::{io, result};
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::header::RegionTableEntry;
|
use super::header::RegionTableEntry;
|
||||||
|
use crate::aligned_file::AlignedFile;
|
||||||
|
|
||||||
const METADATA_SIGN: u64 = 0x6174_6164_6174_656D;
|
const METADATA_SIGN: u64 = 0x6174_6164_6174_656D;
|
||||||
const METADATA_ENTRY_SIZE: usize = 32;
|
const METADATA_ENTRY_SIZE: usize = 32;
|
||||||
@@ -103,17 +103,18 @@ pub struct DiskSpec {
|
|||||||
impl DiskSpec {
|
impl DiskSpec {
|
||||||
/// Parse all metadata from the provided file and store info in DiskSpec
|
/// Parse all metadata from the provided file and store info in DiskSpec
|
||||||
/// structure.
|
/// structure.
|
||||||
pub fn new(f: &mut File, metadata_region: &RegionTableEntry) -> Result<DiskSpec> {
|
pub fn new(f: &AlignedFile, metadata_region: &RegionTableEntry) -> Result<DiskSpec> {
|
||||||
let mut disk_spec = DiskSpec::default();
|
let mut disk_spec = DiskSpec::default();
|
||||||
let mut metadata_presence: u16 = 0;
|
let mut metadata_presence: u16 = 0;
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let metadata = f.metadata().map_err(VhdxMetadataError::ReadMetadata)?;
|
let metadata = f
|
||||||
|
.file()
|
||||||
|
.metadata()
|
||||||
|
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||||
disk_spec.image_size = metadata.len();
|
disk_spec.image_size = metadata.len();
|
||||||
|
|
||||||
let mut buffer = [0u8; METADATA_TABLE_MAX_SIZE];
|
let mut buffer = [0u8; METADATA_TABLE_MAX_SIZE];
|
||||||
f.seek(SeekFrom::Start(metadata_region.file_offset))
|
f.read_exact_at(&mut buffer, metadata_region.file_offset)
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
|
||||||
f.read_exact(&mut buffer)
|
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||||
|
|
||||||
let metadata_header =
|
let metadata_header =
|
||||||
@@ -124,18 +125,16 @@ impl DiskSpec {
|
|||||||
let metadata_entry =
|
let metadata_entry =
|
||||||
MetadataTableEntry::new(&buffer[offset..offset + size_of::<MetadataTableEntry>()])?;
|
MetadataTableEntry::new(&buffer[offset..offset + size_of::<MetadataTableEntry>()])?;
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(
|
let item_offset = metadata_region.file_offset + metadata_entry.offset as u64;
|
||||||
metadata_region.file_offset + metadata_entry.offset as u64,
|
|
||||||
))
|
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
|
||||||
|
|
||||||
if metadata_entry.item_id
|
if metadata_entry.item_id
|
||||||
== Uuid::parse_str(METADATA_FILE_PARAMETER)
|
== Uuid::parse_str(METADATA_FILE_PARAMETER)
|
||||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||||
{
|
{
|
||||||
disk_spec.block_size = f
|
let mut item = [0u8; 2 * size_of::<u32>()];
|
||||||
.read_u32::<LittleEndian>()
|
f.read_exact_at(&mut item, item_offset)
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||||
|
disk_spec.block_size = LittleEndian::read_u32(&item[0..4]);
|
||||||
|
|
||||||
// MUST be at least 1 MiB and not greater than 256 MiB
|
// 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 {
|
if disk_spec.block_size < BLOCK_SIZE_MIN || disk_spec.block_size > BLOCK_SIZE_MAX {
|
||||||
@@ -147,9 +146,7 @@ impl DiskSpec {
|
|||||||
return Err(VhdxMetadataError::InvalidBlockSize);
|
return Err(VhdxMetadataError::InvalidBlockSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bits = f
|
let bits = LittleEndian::read_u32(&item[4..8]);
|
||||||
.read_u32::<LittleEndian>()
|
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
|
||||||
disk_spec.has_parent = bits & BLOCK_HAS_PARENT != 0;
|
disk_spec.has_parent = bits & BLOCK_HAS_PARENT != 0;
|
||||||
|
|
||||||
metadata_presence |= METADATA_FILE_PARAMETER_PRESENT;
|
metadata_presence |= METADATA_FILE_PARAMETER_PRESENT;
|
||||||
@@ -157,27 +154,30 @@ impl DiskSpec {
|
|||||||
== Uuid::parse_str(METADATA_VIRTUAL_DISK_SIZE)
|
== Uuid::parse_str(METADATA_VIRTUAL_DISK_SIZE)
|
||||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||||
{
|
{
|
||||||
disk_spec.virtual_disk_size = f
|
let mut item = [0u8; size_of::<u64>()];
|
||||||
.read_u64::<LittleEndian>()
|
f.read_exact_at(&mut item, item_offset)
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||||
|
disk_spec.virtual_disk_size = LittleEndian::read_u64(&item);
|
||||||
|
|
||||||
metadata_presence |= METADATA_VIRTUAL_DISK_SIZE_PRESENT;
|
metadata_presence |= METADATA_VIRTUAL_DISK_SIZE_PRESENT;
|
||||||
} else if metadata_entry.item_id
|
} else if metadata_entry.item_id
|
||||||
== Uuid::parse_str(METADATA_VIRTUAL_DISK_ID)
|
== Uuid::parse_str(METADATA_VIRTUAL_DISK_ID)
|
||||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||||
{
|
{
|
||||||
disk_spec.disk_id = f
|
let mut item = [0u8; size_of::<u128>()];
|
||||||
.read_u128::<LittleEndian>()
|
f.read_exact_at(&mut item, item_offset)
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||||
|
disk_spec.disk_id = LittleEndian::read_u128(&item);
|
||||||
|
|
||||||
metadata_presence |= METADATA_VIRTUAL_DISK_ID_PRESENT;
|
metadata_presence |= METADATA_VIRTUAL_DISK_ID_PRESENT;
|
||||||
} else if metadata_entry.item_id
|
} else if metadata_entry.item_id
|
||||||
== Uuid::parse_str(METADATA_LOGICAL_SECTOR_SIZE)
|
== Uuid::parse_str(METADATA_LOGICAL_SECTOR_SIZE)
|
||||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||||
{
|
{
|
||||||
disk_spec.logical_sector_size = f
|
let mut item = [0u8; size_of::<u32>()];
|
||||||
.read_u32::<LittleEndian>()
|
f.read_exact_at(&mut item, item_offset)
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||||
|
disk_spec.logical_sector_size = LittleEndian::read_u32(&item);
|
||||||
if !(disk_spec.logical_sector_size == 512 || disk_spec.logical_sector_size == 4096)
|
if !(disk_spec.logical_sector_size == 512 || disk_spec.logical_sector_size == 4096)
|
||||||
{
|
{
|
||||||
return Err(VhdxMetadataError::InvalidLogicalSectorSize);
|
return Err(VhdxMetadataError::InvalidLogicalSectorSize);
|
||||||
@@ -188,9 +188,10 @@ impl DiskSpec {
|
|||||||
== Uuid::parse_str(METADATA_PHYSICAL_SECTOR_SIZE)
|
== Uuid::parse_str(METADATA_PHYSICAL_SECTOR_SIZE)
|
||||||
.map_err(VhdxMetadataError::InvalidUuid)?
|
.map_err(VhdxMetadataError::InvalidUuid)?
|
||||||
{
|
{
|
||||||
disk_spec.physical_sector_size = f
|
let mut item = [0u8; size_of::<u32>()];
|
||||||
.read_u32::<LittleEndian>()
|
f.read_exact_at(&mut item, item_offset)
|
||||||
.map_err(VhdxMetadataError::ReadMetadata)?;
|
.map_err(VhdxMetadataError::ReadMetadata)?;
|
||||||
|
disk_spec.physical_sector_size = LittleEndian::read_u32(&item);
|
||||||
if !(disk_spec.physical_sector_size == 512
|
if !(disk_spec.physical_sector_size == 512
|
||||||
|| disk_spec.physical_sector_size == 4096)
|
|| disk_spec.physical_sector_size == 4096)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use self::header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
|
|||||||
use self::io::VhdxIoError;
|
use self::io::VhdxIoError;
|
||||||
use self::metadata::{DiskSpec, VhdxMetadataError};
|
use self::metadata::{DiskSpec, VhdxMetadataError};
|
||||||
use crate::BlockBackend;
|
use crate::BlockBackend;
|
||||||
|
use crate::aligned_file::AlignedFile;
|
||||||
|
|
||||||
mod bat;
|
mod bat;
|
||||||
mod header;
|
mod header;
|
||||||
@@ -49,7 +50,7 @@ pub type Result<T> = result::Result<T, VhdxError>;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Vhdx {
|
pub struct Vhdx {
|
||||||
file: File,
|
aligned: AlignedFile,
|
||||||
vhdx_header: VhdxHeader,
|
vhdx_header: VhdxHeader,
|
||||||
region_entries: BTreeMap<u64, u64>,
|
region_entries: BTreeMap<u64, u64>,
|
||||||
bat_entry: RegionTableEntry,
|
bat_entry: RegionTableEntry,
|
||||||
@@ -63,11 +64,13 @@ pub struct Vhdx {
|
|||||||
impl Vhdx {
|
impl Vhdx {
|
||||||
/// Parse the Vhdx header, BAT, and metadata from a file and store info
|
/// Parse the Vhdx header, BAT, and metadata from a file and store info
|
||||||
// in Vhdx structure.
|
// in Vhdx structure.
|
||||||
pub fn new(mut file: File) -> Result<Vhdx> {
|
pub fn new(file: File, direct_io: bool) -> Result<Vhdx> {
|
||||||
let vhdx_header = VhdxHeader::new(&mut file).map_err(VhdxError::ParseVhdxHeader)?;
|
let aligned = AlignedFile::new(file, direct_io);
|
||||||
|
|
||||||
|
let vhdx_header = VhdxHeader::new(&aligned).map_err(VhdxError::ParseVhdxHeader)?;
|
||||||
|
|
||||||
let collected_entries = RegionInfo::new(
|
let collected_entries = RegionInfo::new(
|
||||||
&mut file,
|
&aligned,
|
||||||
header::REGION_TABLE_1_START,
|
header::REGION_TABLE_1_START,
|
||||||
vhdx_header.region_entry_count(),
|
vhdx_header.region_entry_count(),
|
||||||
)
|
)
|
||||||
@@ -77,12 +80,12 @@ impl Vhdx {
|
|||||||
let mdr_entry = collected_entries.mdr_entry;
|
let mdr_entry = collected_entries.mdr_entry;
|
||||||
|
|
||||||
let disk_spec =
|
let disk_spec =
|
||||||
DiskSpec::new(&mut file, &mdr_entry).map_err(VhdxError::ParseVhdxMetadata)?;
|
DiskSpec::new(&aligned, &mdr_entry).map_err(VhdxError::ParseVhdxMetadata)?;
|
||||||
let bat_entries = BatEntry::collect_bat_entries(&mut file, &disk_spec, &bat_entry)
|
let bat_entries = BatEntry::collect_bat_entries(&aligned, &disk_spec, &bat_entry)
|
||||||
.map_err(VhdxError::ReadBatEntry)?;
|
.map_err(VhdxError::ReadBatEntry)?;
|
||||||
|
|
||||||
Ok(Vhdx {
|
Ok(Vhdx {
|
||||||
file,
|
aligned,
|
||||||
vhdx_header,
|
vhdx_header,
|
||||||
region_entries: collected_entries.region_entries,
|
region_entries: collected_entries.region_entries,
|
||||||
bat_entry,
|
bat_entry,
|
||||||
@@ -107,7 +110,7 @@ impl Read for Vhdx {
|
|||||||
let sector_index = self.current_offset / 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(
|
let result = io::read(
|
||||||
&mut self.file,
|
&self.aligned,
|
||||||
buf,
|
buf,
|
||||||
&self.disk_spec,
|
&self.disk_spec,
|
||||||
&self.bat_entries,
|
&self.bat_entries,
|
||||||
@@ -128,7 +131,7 @@ impl Read for Vhdx {
|
|||||||
|
|
||||||
impl Write for Vhdx {
|
impl Write for Vhdx {
|
||||||
fn flush(&mut self) -> IoResult<()> {
|
fn flush(&mut self) -> IoResult<()> {
|
||||||
self.file.flush()
|
self.aligned.file_mut().flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper function to satisfy Write trait implementation for VHDx disk.
|
/// Wrapper function to satisfy Write trait implementation for VHDx disk.
|
||||||
@@ -140,12 +143,12 @@ impl Write for Vhdx {
|
|||||||
if self.first_write {
|
if self.first_write {
|
||||||
self.first_write = false;
|
self.first_write = false;
|
||||||
self.vhdx_header
|
self.vhdx_header
|
||||||
.update(&mut self.file)
|
.update(&self.aligned)
|
||||||
.map_err(|e| IoError::other(format!("Failed to update VHDx header: {e}")))?;
|
.map_err(|e| IoError::other(format!("Failed to update VHDx header: {e}")))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = io::write(
|
let result = io::write(
|
||||||
&mut self.file,
|
&self.aligned,
|
||||||
buf,
|
buf,
|
||||||
&mut self.disk_spec,
|
&mut self.disk_spec,
|
||||||
self.bat_entry.file_offset,
|
self.bat_entry.file_offset,
|
||||||
@@ -210,7 +213,8 @@ impl BlockBackend for Vhdx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn physical_size(&self) -> result::Result<u64, crate::Error> {
|
fn physical_size(&self) -> result::Result<u64, crate::Error> {
|
||||||
self.file
|
self.aligned
|
||||||
|
.file()
|
||||||
.metadata()
|
.metadata()
|
||||||
.map(|m| m.len())
|
.map(|m| m.len())
|
||||||
.map_err(crate::Error::GetFileMetadata)
|
.map_err(crate::Error::GetFileMetadata)
|
||||||
@@ -220,7 +224,7 @@ impl BlockBackend for Vhdx {
|
|||||||
impl Clone for Vhdx {
|
impl Clone for Vhdx {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Vhdx {
|
Vhdx {
|
||||||
file: self.file.try_clone().unwrap(),
|
aligned: self.aligned.try_clone().unwrap(),
|
||||||
vhdx_header: self.vhdx_header.clone(),
|
vhdx_header: self.vhdx_header.clone(),
|
||||||
region_entries: self.region_entries.clone(),
|
region_entries: self.region_entries.clone(),
|
||||||
bat_entry: self.bat_entry,
|
bat_entry: self.bat_entry,
|
||||||
@@ -235,7 +239,7 @@ impl Clone for Vhdx {
|
|||||||
|
|
||||||
impl AsRawFd for Vhdx {
|
impl AsRawFd for Vhdx {
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
self.file.as_raw_fd()
|
self.aligned.file().as_raw_fd()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,3 +254,64 @@ pub(crate) fn uuid_from_guid(buf: &[u8]) -> Uuid {
|
|||||||
buf[8..16].try_into().unwrap(),
|
buf[8..16].try_into().unwrap(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use vmm_sys_util::tempfile::TempFile;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Generate a small dynamic VHDX with `qemu-img`. Returns `None` (and the
|
||||||
|
/// test is skipped) when `qemu-img` is unavailable, e.g. in minimal CI.
|
||||||
|
fn dynamic_vhdx(size_mib: u64) -> Option<TempFile> {
|
||||||
|
let tf = TempFile::new().unwrap();
|
||||||
|
let path = tf.as_path();
|
||||||
|
let status = Command::new("qemu-img")
|
||||||
|
.args(["create", "-f", "vhdx", "-o", "subformat=dynamic"])
|
||||||
|
.arg(path)
|
||||||
|
.arg(format!("{size_mib}M"))
|
||||||
|
.status();
|
||||||
|
match status {
|
||||||
|
Ok(s) if s.success() => Some(tf),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An unaligned sector write under a forced O_DIRECT alignment must go
|
||||||
|
/// through `AlignedFile`'s read-modify-write bounce (the data block and the
|
||||||
|
/// BAT update both land at unaligned host offsets) and read back intact.
|
||||||
|
#[test]
|
||||||
|
fn unaligned_write_is_rmw() {
|
||||||
|
let Some(tf) = dynamic_vhdx(16) else {
|
||||||
|
eprintln!("skipping unaligned_write_is_rmw: qemu-img unavailable");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = std::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(tf.as_path())
|
||||||
|
.unwrap();
|
||||||
|
let mut vhdx = Vhdx::new(file, false).unwrap();
|
||||||
|
|
||||||
|
// Force a non-zero alignment so all of vhdx's positioned I/O exercises
|
||||||
|
// the bounce/RMW path even though the tempfile is not really O_DIRECT.
|
||||||
|
vhdx.aligned = AlignedFile::with_alignment(vhdx.aligned.file().try_clone().unwrap(), 512);
|
||||||
|
|
||||||
|
let sector = vhdx.disk_spec.logical_sector_size as usize;
|
||||||
|
let data: Vec<u8> = (0..sector).map(|i| ((i + 1) % 251) as u8).collect();
|
||||||
|
|
||||||
|
// Write at virtual offset 0 (allocates a new data block + rewrites BAT).
|
||||||
|
vhdx.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
assert_eq!(vhdx.write(&data).unwrap(), data.len());
|
||||||
|
vhdx.flush().unwrap();
|
||||||
|
|
||||||
|
// Read it back through a fresh, forced-alignment handle.
|
||||||
|
let mut readback = vec![0u8; sector];
|
||||||
|
vhdx.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
assert_eq!(vhdx.read(&mut readback).unwrap(), readback.len());
|
||||||
|
assert_eq!(readback, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ pub struct VhdxDisk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VhdxDisk {
|
impl VhdxDisk {
|
||||||
pub fn new(f: File) -> BlockResult<Self> {
|
pub fn new(f: File, direct_io: bool) -> BlockResult<Self> {
|
||||||
Ok(VhdxDisk {
|
Ok(VhdxDisk {
|
||||||
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f).map_err(|e| {
|
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f, direct_io).map_err(|e| {
|
||||||
let kind = match &e {
|
let kind = match &e {
|
||||||
VhdxError::NotVhdx(_)
|
VhdxError::NotVhdx(_)
|
||||||
| VhdxError::ParseVhdxHeader(_)
|
| VhdxError::ParseVhdxHeader(_)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ fuzz_target!(|bytes: &[u8]| -> Corpus {
|
|||||||
disk_file.write_all(&bytes[..]).unwrap();
|
disk_file.write_all(&bytes[..]).unwrap();
|
||||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
|
||||||
let mut vhdx = match Vhdx::new(disk_file) {
|
let mut vhdx = match Vhdx::new(disk_file, false) {
|
||||||
Ok(vhdx) => vhdx,
|
Ok(vhdx) => vhdx,
|
||||||
Err(_) => return Corpus::Reject,
|
Err(_) => return Corpus::Reject,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user