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:
Anatol Belski
2026-04-25 00:39:24 +02:00
committed by Rob Bradford
parent 1ca0c39b4d
commit b595f1dbc3
11 changed files with 163 additions and 140 deletions

View File

@@ -22,8 +22,8 @@ use crate::disk_file::AsyncFullDiskFile;
use crate::error::{BlockError, BlockErrorKind, BlockResult};
use crate::formats::raw::{RawBackend, RawDisk};
use crate::formats::vhd::VhdDisk;
use crate::formats::vhdx::VhdxDisk;
use crate::qcow_disk::QcowDisk;
use crate::vhdx_sync::VhdxDisk;
use crate::{
ImageType, block_aio_is_supported, detect_image_type, open_disk_image, preallocate_disk,
};

View File

@@ -9,3 +9,4 @@
pub mod raw;
pub mod vhd;
pub mod vhdx;

View File

@@ -10,8 +10,8 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use remain::sorted;
use thiserror::Error;
use crate::vhdx::vhdx_header::RegionTableEntry;
use crate::vhdx::vhdx_metadata::DiskSpec;
use super::header::RegionTableEntry;
use super::metadata::DiskSpec;
// Payload BAT Entry States
pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;

View File

@@ -341,7 +341,7 @@ impl 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 = crate::vhdx::uuid_from_guid(buffer);
let uuid = super::uuid_from_guid(buffer);
region_table_entry.guid = uuid;
Ok(region_table_entry)

View File

@@ -8,8 +8,8 @@ use std::io::{self, Read, Seek, SeekFrom, Write};
use remain::sorted;
use thiserror::Error;
use crate::vhdx::vhdx_bat::{self, BatEntry, VhdxBatError};
use crate::vhdx::vhdx_metadata::{self, DiskSpec};
use super::bat::{self, BatEntry, VhdxBatError};
use super::metadata::{self, DiskSpec};
const SECTOR_SIZE: u64 = 512;
@@ -74,7 +74,7 @@ impl Sector {
return Err(VhdxIoError::InvalidBatIndex);
}
};
sector.file_offset = bat_entry & vhdx_bat::BAT_FILE_OFF_MASK;
sector.file_offset = bat_entry & bat::BAT_FILE_OFF_MASK;
if sector.file_offset != 0 {
sector.file_offset += sector.block_offset;
}
@@ -108,12 +108,12 @@ pub fn read(
}
};
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
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(
@@ -122,7 +122,7 @@ pub fn read(
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {
@@ -162,13 +162,12 @@ pub fn write(
}
};
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
let file_offset =
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
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)?;
@@ -176,12 +175,12 @@ pub fn write(
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
disk_spec.image_size = new_size;
let new_bat_entry = file_offset
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
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 < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
if file_offset < metadata::BLOCK_SIZE_MIN as u64 {
break;
}
@@ -192,8 +191,8 @@ pub fn write(
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
if sector.file_offset < metadata::BLOCK_SIZE_MIN as u64 {
break;
}
@@ -204,7 +203,7 @@ pub fn write(
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {

View File

@@ -11,7 +11,7 @@ use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
use crate::vhdx::vhdx_header::RegionTableEntry;
use super::header::RegionTableEntry;
const METADATA_SIGN: u64 = 0x6174_6164_6174_656D;
const METADATA_ENTRY_SIZE: usize = 32;
@@ -315,7 +315,7 @@ impl 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 = crate::vhdx::uuid_from_guid(buffer);
let uuid = super::uuid_from_guid(buffer);
metadata_table_entry.item_id = uuid;
if metadata_table_entry.length > METADATA_LENGTH_MAX {

View File

@@ -12,16 +12,16 @@ 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;
use crate::vhdx::vhdx_bat::{BatEntry, VhdxBatError};
use crate::vhdx::vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
use crate::vhdx::vhdx_io::VhdxIoError;
use crate::vhdx::vhdx_metadata::{DiskSpec, VhdxMetadataError};
mod vhdx_bat;
mod vhdx_header;
mod vhdx_io;
mod vhdx_metadata;
mod bat;
mod header;
mod io;
mod metadata;
#[sorted]
#[derive(Error, Debug)]
@@ -65,7 +65,7 @@ impl Vhdx {
let collected_entries = RegionInfo::new(
&mut file,
vhdx_header::REGION_TABLE_1_START,
header::REGION_TABLE_1_START,
vhdx_header.region_entry_count(),
)
.map_err(VhdxError::ParseVhdxRegionEntry)?;
@@ -103,7 +103,7 @@ impl Read for Vhdx {
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 = vhdx_io::read(
let result = io::read(
&mut self.file,
buf,
&self.disk_spec,
@@ -141,7 +141,7 @@ impl Write for Vhdx {
.map_err(|e| std::io::Error::other(format!("Failed to update VHDx header: {e}")))?;
}
let result = vhdx_io::write(
let result = io::write(
&mut self.file,
buf,
&mut self.disk_spec,

View 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))))
}
}

View 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;

View File

@@ -5,106 +5,13 @@
// SPDX-License-Identifier: Apache-2.0
use std::collections::VecDeque;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::fd::AsRawFd;
use std::sync::{Arc, Mutex};
use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, BorrowedDiskFd,
DiskFileError,
};
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
use crate::vhdx::{Vhdx, VhdxError};
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))))
}
}
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
use crate::formats::vhdx::internal::Vhdx;
pub struct VhdxSync {
vhdx_file: Arc<Mutex<Vhdx>>,

View File

@@ -22,10 +22,6 @@ pub(crate) mod qcow_common;
pub mod qcow_disk;
pub(crate) mod qcow_sync;
mod sparse;
pub use formats::raw as raw_disk;
pub mod vhdx;
pub mod vhdx_sync;
use std::alloc::{Layout, alloc_zeroed};
use std::fmt::{self, Debug};
use std::fs::{File, OpenOptions};
@@ -37,6 +33,8 @@ use std::path::Path;
use std::str::FromStr;
use std::{cmp, mem, result};
pub use formats::raw as raw_disk;
pub use formats::vhdx::internal as vhdx;
#[cfg(feature = "io_uring")]
use io_uring::{IoUring, Probe, opcode};
use libc::{
@@ -54,8 +52,8 @@ use vmm_sys_util::{aio, ioctl_io_nr, ioctl_ior_nr};
use crate::async_io::AsyncIoError;
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
use crate::formats::vhdx::VhdxError;
use crate::request::SECTOR_SIZE;
use crate::vhdx::VhdxError;
#[derive(Error, Debug)]
pub enum Error {