block: qcow: Flatten internal and worker modules

Remove the internal and worker submodule layers from the QCOW2
format directory. The former internal files become direct children
of qcow, with internal/mod.rs turning into parser.rs. The worker
backends move up as engine_sync.rs and engine_uring.rs, and
worker/mod.rs, which held only module declarations, is dropped.

The public parser types are now surfaced at the qcow module level,
so external callers use block::formats::qcow instead of reaching
into the internal module.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2026-07-07 18:11:38 +02:00
committed by Rob Bradford
parent b5aeabe77c
commit 7120311462
17 changed files with 125 additions and 127 deletions
@@ -14,7 +14,7 @@ use std::sync::Arc;
use super::decoder::Decoder; use super::decoder::Decoder;
use super::metadata::{BackingRead, ClusterReadMapping, QcowMetadata}; use super::metadata::{BackingRead, ClusterReadMapping, QcowMetadata};
use super::{BackingFile, BackingKind, Error as QcowError}; use super::parser::{BackingFile, BackingKind, Error as QcowError};
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
use crate::formats::qcow::common::decompress_cluster; use crate::formats::qcow::common::decompress_cluster;
@@ -146,7 +146,7 @@ impl Drop for Qcow2Backing {
} }
/// Construct a thread safe backing file reader. /// Construct a thread safe backing file reader.
pub fn shared_backing_from(bf: BackingFile) -> BlockResult<Arc<dyn BackingRead>> { pub(super) fn shared_backing_from(bf: BackingFile) -> BlockResult<Arc<dyn BackingRead>> {
let (kind, virtual_size) = bf.into_kind(); let (kind, virtual_size) = bf.into_kind();
let dup_fd = |fd: BorrowedFd<'_>| -> BlockResult<OwnedFd> { let dup_fd = |fd: BorrowedFd<'_>| -> BlockResult<OwnedFd> {
+3 -5
View File
@@ -10,16 +10,14 @@
use std::io; use std::io;
#[cfg(test)] use super::decoder::Decoder;
use super::internal;
use super::internal::decoder::Decoder;
/// Decompress a full QCOW2 cluster from compressed data. /// Decompress a full QCOW2 cluster from compressed data.
/// ///
/// Returns a `cluster_size` byte buffer with the decompressed cluster /// Returns a `cluster_size` byte buffer with the decompressed cluster
/// content. Fails if the decoder does not produce exactly `cluster_size` /// content. Fails if the decoder does not produce exactly `cluster_size`
/// bytes. /// bytes.
pub fn decompress_cluster( pub(super) fn decompress_cluster(
compressed: &[u8], compressed: &[u8],
cluster_size: usize, cluster_size: usize,
decoder: &dyn Decoder, decoder: &dyn Decoder,
@@ -43,8 +41,8 @@ pub(crate) mod unit_tests {
use flate2::Compression; use flate2::Compression;
use flate2::write::DeflateEncoder; use flate2::write::DeflateEncoder;
use super::super::decoder::ZlibDecoder;
use super::decompress_cluster; use super::decompress_cluster;
use super::internal::decoder::ZlibDecoder;
const COMPRESSED_FLAG: u64 = 1 << 62; const COMPRESSED_FLAG: u64 = 1 << 62;
const CLUSTER_USED_FLAG: u64 = 1 << 63; const CLUSTER_USED_FLAG: u64 = 1 << 63;
@@ -18,7 +18,7 @@ pub enum Error {
ZstdFillBuffer(#[source] io::Error), ZstdFillBuffer(#[source] io::Error),
} }
pub type Result<T> = result::Result<T, Error>; pub(super) type Result<T> = result::Result<T, Error>;
/// Generic trait for decoding zlib/zstd formats /// Generic trait for decoding zlib/zstd formats
pub trait Decoder: Send + Sync { pub trait Decoder: Send + Sync {
@@ -26,7 +26,7 @@ pub trait Decoder: Send + Sync {
} }
#[derive(Default)] #[derive(Default)]
pub struct ZlibDecoder {} pub(super) struct ZlibDecoder {}
impl Decoder for ZlibDecoder { impl Decoder for ZlibDecoder {
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> { fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
@@ -45,7 +45,7 @@ impl Decoder for ZlibDecoder {
} }
#[derive(Default)] #[derive(Default)]
pub struct ZstdDecoder {} pub(super) struct ZstdDecoder {}
impl Decoder for ZstdDecoder { impl Decoder for ZstdDecoder {
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> { fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
@@ -13,14 +13,14 @@ use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
use super::common::decompress_cluster; use super::common::decompress_cluster;
use super::internal::decoder::Decoder; use super::decoder::Decoder;
use super::internal::metadata::{ use super::metadata::{
BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata,
}; };
use super::internal::qcow_raw_file::QcowRawFile; use super::qcow_raw_file::QcowRawFile;
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult}; use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
pub struct QcowSync { pub(super) struct QcowSync {
metadata: Arc<QcowMetadata>, metadata: Arc<QcowMetadata>,
data_file: QcowRawFile, data_file: QcowRawFile,
/// See the backing_file field on QcowDisk. /// See the backing_file field on QcowDisk.
@@ -322,10 +322,9 @@ mod unit_tests {
use crate::error::BlockErrorKind; use crate::error::BlockErrorKind;
use crate::formats::qcow; use crate::formats::qcow;
use crate::formats::qcow::common::unit_tests::compress_allocated_clusters; use crate::formats::qcow::common::unit_tests::compress_allocated_clusters;
use crate::formats::qcow::internal::{ use crate::formats::qcow::{
BackingFileConfig, Error as QcowError, ImageType, QcowHeader, BackingFileConfig, Error as QcowError, ImageType, QcowDisk, QcowHeader, QcowTempDisk,
}; };
use crate::formats::qcow::{QcowDisk, QcowTempDisk};
const TEST_L1_L2_ADDR_MASK: u64 = 0x00ff_ffff_ffff_fe00; const TEST_L1_L2_ADDR_MASK: u64 = 0x00ff_ffff_ffff_fe00;
const TEST_HEADER_L1_TABLE_OFFSET: u64 = 40; const TEST_HEADER_L1_TABLE_OFFSET: u64 = 40;
@@ -18,11 +18,11 @@ use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
use super::common::decompress_cluster; use super::common::decompress_cluster;
use super::internal::decoder::Decoder; use super::decoder::Decoder;
use super::internal::metadata::{ use super::metadata::{
BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata,
}; };
use super::internal::qcow_raw_file::QcowRawFile; use super::qcow_raw_file::QcowRawFile;
use crate::async_io::{ use crate::async_io::{
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, UringDataIo, AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, UringDataIo,
}; };
@@ -36,7 +36,7 @@ use crate::async_io::{
/// ///
/// Writes are synchronous because metadata allocation must complete /// Writes are synchronous because metadata allocation must complete
/// before the host offset is known. /// before the host offset is known.
pub struct QcowAsync { pub(super) struct QcowAsync {
metadata: Arc<QcowMetadata>, metadata: Arc<QcowMetadata>,
// Drop before data_file so pending SQEs can be submitted while fd is valid. // Drop before data_file so pending SQEs can be submitted while fd is valid.
data_io: UringDataIo, data_io: UringDataIo,
@@ -483,8 +483,7 @@ mod unit_tests {
use crate::async_io::{AsyncIoCompletion, AsyncIoOperation, GuestMemoryTarget, OwnedIoBuffer}; use crate::async_io::{AsyncIoCompletion, AsyncIoOperation, GuestMemoryTarget, OwnedIoBuffer};
use crate::disk_file::AsyncDiskFile; use crate::disk_file::AsyncDiskFile;
use crate::formats::qcow::common::unit_tests::compress_allocated_clusters; use crate::formats::qcow::common::unit_tests::compress_allocated_clusters;
use crate::formats::qcow::internal::{BackingFileConfig, ImageType}; use crate::formats::qcow::{BackingFileConfig, ImageType, QcowDisk, QcowTempDisk};
use crate::formats::qcow::{QcowDisk, QcowTempDisk};
fn create_disk_with_data( fn create_disk_with_data(
file_size: u64, file_size: u64,
@@ -16,8 +16,9 @@ use bitflags::bitflags;
use vmm_sys_util::file_traits::FileSync; use vmm_sys_util::file_traits::FileSync;
use super::decoder::{Decoder, ZlibDecoder, ZstdDecoder}; use super::decoder::{Decoder, ZlibDecoder, ZstdDecoder};
use super::parser::{Error, Result};
use super::qcow_raw_file::BeUint; use super::qcow_raw_file::BeUint;
use super::{Error, Result, div_round_up_u32, div_round_up_u64}; use super::util::{div_round_up_u32, div_round_up_u64};
use crate::aligned_file::AlignedFile; use crate::aligned_file::AlignedFile;
use crate::error::{BlockError, BlockErrorKind, BlockResult}; use crate::error::{BlockError, BlockErrorKind, BlockResult};
@@ -41,7 +41,7 @@ use super::{QcowHeader, refcount};
/// the actual data I/O using its own per queue file descriptor without /// the actual data I/O using its own per queue file descriptor without
/// holding the metadata lock. /// holding the metadata lock.
#[derive(Debug)] #[derive(Debug)]
pub enum ClusterReadMapping { pub(super) enum ClusterReadMapping {
/// The cluster is not allocated and the guest should see zeros. /// The cluster is not allocated and the guest should see zeros.
/// This covers both truly unallocated clusters where the L1 or L2 /// This covers both truly unallocated clusters where the L1 or L2
/// entry is zero and clusters with the ZERO flag set. /// entry is zero and clusters with the ZERO flag set.
@@ -76,7 +76,7 @@ pub enum ClusterReadMapping {
/// the actual data I/O using its own per queue file descriptor without /// the actual data I/O using its own per queue file descriptor without
/// holding the metadata lock. /// holding the metadata lock.
#[derive(Debug)] #[derive(Debug)]
pub enum ClusterWriteMapping { pub(super) enum ClusterWriteMapping {
/// The write target is at the given host file offset. /// The write target is at the given host file offset.
/// This covers both already allocated clusters and freshly allocated ones. /// This covers both already allocated clusters and freshly allocated ones.
/// The offset is the exact byte position combining cluster base and /// The offset is the exact byte position combining cluster base and
@@ -94,7 +94,7 @@ pub(crate) trait BackingRead: Send + Sync {
/// Action that the caller must perform after deallocate_bytes. /// Action that the caller must perform after deallocate_bytes.
#[derive(Debug)] #[derive(Debug)]
pub enum DeallocAction { pub(super) enum DeallocAction {
/// Punch a hole at the given host file offset for a full cluster. /// Punch a hole at the given host file offset for a full cluster.
PunchHole { host_offset: u64, length: u64 }, PunchHole { host_offset: u64, length: u64 },
/// Write zeros at the given host file offset for a partial cluster. /// Write zeros at the given host file offset for a partial cluster.
@@ -115,7 +115,7 @@ pub enum DeallocAction {
/// L1 to L2 lookup, which completes under a shared read lock. Only /// L1 to L2 lookup, which completes under a shared read lock. Only
/// cluster allocation, L2 cache eviction and resize take the exclusive /// cluster allocation, L2 cache eviction and resize take the exclusive
/// write lock, so contention stays low and queues scale. /// write lock, so contention stays low and queues scale.
pub struct QcowMetadata { pub(super) struct QcowMetadata {
inner: RwLock<QcowState>, inner: RwLock<QcowState>,
decoder: Arc<dyn Decoder>, decoder: Arc<dyn Decoder>,
} }
@@ -160,7 +160,7 @@ impl QcowMetadata {
/// ///
/// The has_backing_file flag indicates whether a backing file exists, /// The has_backing_file flag indicates whether a backing file exists,
/// needed to distinguish zero versus backing for unallocated clusters. /// needed to distinguish zero versus backing for unallocated clusters.
pub fn map_clusters_for_read( pub(super) fn map_clusters_for_read(
&self, &self,
address: u64, address: u64,
total_length: usize, total_length: usize,
@@ -228,7 +228,7 @@ impl QcowMetadata {
/// unallocated and a backing file exists, the caller should have already /// unallocated and a backing file exists, the caller should have already
/// read the backing cluster data and pass it here. If None, the new /// read the backing cluster data and pass it here. If None, the new
/// cluster is zeroed. /// cluster is zeroed.
pub fn map_cluster_for_write( pub(super) fn map_cluster_for_write(
&self, &self,
address: u64, address: u64,
backing_data: Option<Vec<u8>>, backing_data: Option<Vec<u8>>,
@@ -237,7 +237,7 @@ impl QcowMetadata {
inner.map_write(address, backing_data) inner.map_write(address, backing_data)
} }
pub fn flush(&self) -> io::Result<()> { pub(super) fn flush(&self) -> io::Result<()> {
let mut inner = self.inner.write().unwrap(); let mut inner = self.inner.write().unwrap();
inner.sync_caches()?; inner.sync_caches()?;
let mut unref = mem::take(&mut inner.unref_clusters); let mut unref = mem::take(&mut inner.unref_clusters);
@@ -247,7 +247,7 @@ impl QcowMetadata {
/// Flushes dirty metadata caches and clears the dirty bit for /// Flushes dirty metadata caches and clears the dirty bit for
/// clean shutdown. /// clean shutdown.
pub fn shutdown(&self) { pub(super) fn shutdown(&self) {
let mut inner = self.inner.write().unwrap(); let mut inner = self.inner.write().unwrap();
let _ = inner.sync_caches(); let _ = inner.sync_caches();
let QcowState { let QcowState {
@@ -265,7 +265,7 @@ impl QcowMetadata {
/// clusters beyond the new size and risks data loss. /// clusters beyond the new size and risks data loss.
/// ///
/// Returns an error if the new size is smaller than the current size. /// Returns an error if the new size is smaller than the current size.
pub fn resize(&self, new_size: u64) -> io::Result<()> { pub(super) fn resize(&self, new_size: u64) -> io::Result<()> {
let mut inner = self.inner.write().unwrap(); let mut inner = self.inner.write().unwrap();
inner.resize(new_size) inner.resize(new_size)
} }
@@ -339,16 +339,16 @@ impl QcowMetadata {
Ok(actions) Ok(actions)
} }
pub fn virtual_size(&self) -> u64 { pub(super) fn virtual_size(&self) -> u64 {
self.inner.read().unwrap().header.size self.inner.read().unwrap().header.size
} }
pub fn cluster_size(&self) -> u64 { pub(super) fn cluster_size(&self) -> u64 {
self.inner.read().unwrap().raw_file.cluster_size() self.inner.read().unwrap().raw_file.cluster_size()
} }
/// Returns the shared decoder matching the image compression type. /// Returns the shared decoder matching the image compression type.
pub fn decoder(&self) -> Arc<dyn Decoder> { pub(super) fn decoder(&self) -> Arc<dyn Decoder> {
Arc::clone(&self.decoder) Arc::clone(&self.decoder)
} }
+24 -12
View File
@@ -7,9 +7,19 @@
//! Provides [`QcowDisk`], the `DiskFile` wrapper for QCOW2 images //! Provides [`QcowDisk`], the `DiskFile` wrapper for QCOW2 images
//! with backing file and compression support. //! with backing file and compression support.
pub(crate) mod common; mod backing;
pub mod internal; mod common;
pub mod worker; mod decoder;
mod engine_sync;
#[cfg(feature = "io_uring")]
mod engine_uring;
mod header;
mod metadata;
mod parser;
mod qcow_raw_file;
mod refcount;
mod util;
mod vec_cache;
use std::fs::File; use std::fs::File;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
@@ -18,20 +28,22 @@ use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, io}; use std::{fmt, io};
pub use parser::{
BackingFileConfig, CompressionType, Error, ImageType, IncompatFeatures, MissingFeatureError,
QcowHeader,
};
#[cfg(any(test, feature = "test-utils"))] #[cfg(any(test, feature = "test-utils"))]
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap}; use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
#[cfg(any(test, feature = "test-utils"))] #[cfg(any(test, feature = "test-utils"))]
use vmm_sys_util::tempfile::TempFile; use vmm_sys_util::tempfile::TempFile;
use self::internal::backing::shared_backing_from; use self::backing::shared_backing_from;
use self::internal::metadata::{BackingRead, QcowMetadata}; use self::engine_sync::QcowSync;
use self::internal::qcow_raw_file::QcowRawFile;
#[cfg(any(test, feature = "test-utils"))]
use self::internal::{BackingFileConfig, QcowHeader};
use self::internal::{MAX_NESTING_DEPTH, parse_qcow};
#[cfg(feature = "io_uring")] #[cfg(feature = "io_uring")]
use self::worker::async_uring::QcowAsync; use self::engine_uring::QcowAsync;
use self::worker::sync::QcowSync; use self::metadata::{BackingRead, QcowMetadata};
use self::parser::{MAX_NESTING_DEPTH, parse_qcow};
use self::qcow_raw_file::QcowRawFile;
use crate::aligned_file::AlignedFile; use crate::aligned_file::AlignedFile;
#[cfg(any(test, feature = "test-utils"))] #[cfg(any(test, feature = "test-utils"))]
use crate::async_io::GuestMemoryTarget; use crate::async_io::GuestMemoryTarget;
@@ -142,7 +154,7 @@ impl QcowDisk {
} }
#[cfg(test)] #[cfg(test)]
pub(crate) fn metadata(&self) -> &QcowMetadata { fn metadata(&self) -> &QcowMetadata {
&self.metadata &self.metadata
} }
} }
@@ -4,15 +4,6 @@
// //
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
pub(crate) mod backing;
pub(crate) mod decoder;
mod header;
pub(crate) mod metadata;
pub(crate) mod qcow_raw_file;
mod refcount;
mod util;
mod vec_cache;
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::fs::{OpenOptions, read_link}; use std::fs::{OpenOptions, read_link};
@@ -21,24 +12,25 @@ use std::os::unix::fs::FileExt;
use std::path::Path; use std::path::Path;
use std::{io, result, str}; use std::{io, result, str};
pub use header::{ use log::warn;
use remain::sorted;
use thiserror::Error;
pub use super::header::{
BackingFileConfig, CompressionType, ImageType, IncompatFeatures, MissingFeatureError, BackingFileConfig, CompressionType, ImageType, IncompatFeatures, MissingFeatureError,
QcowHeader, QcowHeader,
}; };
use header::{ use super::header::{
COMPATIBLE_FEATURES_LAZY_REFCOUNTS, MAX_CLUSTER_BITS, MAX_QCOW_FILE_SIZE, COMPATIBLE_FEATURES_LAZY_REFCOUNTS, MAX_CLUSTER_BITS, MAX_QCOW_FILE_SIZE,
MAX_RAM_POINTER_TABLE_SIZE, MIN_CLUSTER_BITS, QCOW_MAGIC, max_refcount_clusters, MAX_RAM_POINTER_TABLE_SIZE, MIN_CLUSTER_BITS, QCOW_MAGIC, max_refcount_clusters,
offset_is_cluster_boundary, offset_is_cluster_boundary,
}; };
use log::warn; use super::qcow_raw_file::QcowRawFile;
use qcow_raw_file::QcowRawFile; use super::refcount::RefCount;
use refcount::RefCount; pub(crate) use super::util::MAX_NESTING_DEPTH;
use remain::sorted; use super::util::{L1_TABLE_OFFSET_MASK, L2_TABLE_OFFSET_MASK, div_round_up_u64};
use thiserror::Error; use super::vec_cache::{CacheMap, VecCache};
pub(crate) use util::MAX_NESTING_DEPTH; use super::{metadata, refcount};
use util::{L1_TABLE_OFFSET_MASK, L2_TABLE_OFFSET_MASK, div_round_up_u32, div_round_up_u64};
use vec_cache::{CacheMap, VecCache};
use crate::aligned_file::AlignedFile; use crate::aligned_file::AlignedFile;
use crate::error::{BlockError, BlockErrorKind, BlockResult}; use crate::error::{BlockError, BlockErrorKind, BlockResult};
use crate::query_device_size; use crate::query_device_size;
@@ -156,7 +148,7 @@ pub enum Error {
WritingHeader(#[source] io::Error), WritingHeader(#[source] io::Error),
} }
pub type Result<T> = result::Result<T, Error>; pub(super) type Result<T> = result::Result<T, Error>;
/// Concrete backing file variants. /// Concrete backing file variants.
pub(crate) enum BackingKind { pub(crate) enum BackingKind {
@@ -865,7 +857,7 @@ fn rebuild_refcounts(raw_file: &mut QcowRawFile, header: QcowHeader) -> BlockRes
} }
/// Detect the type of an image file by checking for a valid qcow2 header. /// Detect the type of an image file by checking for a valid qcow2 header.
pub fn detect_image_type(file: &mut AlignedFile) -> BlockResult<ImageType> { pub(super) fn detect_image_type(file: &mut AlignedFile) -> BlockResult<ImageType> {
let mut magic_bytes = [0u8; 4]; let mut magic_bytes = [0u8; 4];
file.read_exact_at(&mut magic_bytes, 0) file.read_exact_at(&mut magic_bytes, 0)
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?; .map_err(|e| BlockError::new(BlockErrorKind::Io, Error::ReadingHeader(e)))?;
@@ -888,11 +880,11 @@ mod unit_tests {
use vmm_sys_util::tempdir::TempDir; use vmm_sys_util::tempdir::TempDir;
use vmm_sys_util::tempfile::TempFile; use vmm_sys_util::tempfile::TempFile;
use super::header::{ use super::super::header::{
AUTOCLEAR_FEATURES_OFFSET, DEFAULT_CLUSTER_BITS, DEFAULT_REFCOUNT_ORDER, AUTOCLEAR_FEATURES_OFFSET, DEFAULT_CLUSTER_BITS, DEFAULT_REFCOUNT_ORDER,
HEADER_EXT_BACKING_FORMAT, HEADER_EXT_END, V2_BARE_HEADER_SIZE, V3_BARE_HEADER_SIZE, HEADER_EXT_BACKING_FORMAT, HEADER_EXT_END, V2_BARE_HEADER_SIZE, V3_BARE_HEADER_SIZE,
}; };
use super::util::ZERO_FLAG; use super::super::util::{self, ZERO_FLAG};
use super::*; use super::*;
use crate::formats::qcow::{QcowDisk, QcowTempDisk}; use crate::formats::qcow::{QcowDisk, QcowTempDisk};
@@ -149,7 +149,7 @@ fn write_refcount_subbyte<const BITS: usize>(
/// A qcow file. Allows reading/writing clusters and appending clusters. /// A qcow file. Allows reading/writing clusters and appending clusters.
#[derive(Debug)] #[derive(Debug)]
pub struct QcowRawFile { pub(super) struct QcowRawFile {
file: AlignedFile, file: AlignedFile,
cluster_size: u64, cluster_size: u64,
cluster_mask: u64, cluster_mask: u64,
@@ -161,7 +161,7 @@ pub struct QcowRawFile {
impl QcowRawFile { impl QcowRawFile {
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not /// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
/// a power of two or refcount_bits is invalid. /// a power of two or refcount_bits is invalid.
pub fn from(file: AlignedFile, cluster_size: u64, refcount_bits: u64) -> Option<Self> { pub(super) fn from(file: AlignedFile, cluster_size: u64, refcount_bits: u64) -> Option<Self> {
if !cluster_size.is_power_of_two() { if !cluster_size.is_power_of_two() {
return None; return None;
} }
@@ -193,7 +193,7 @@ impl QcowRawFile {
/// Reads `count` 64 bit offsets and returns them as a vector. /// Reads `count` 64 bit offsets and returns them as a vector.
/// `mask` optionally `&`s out some of the bits on the file. /// `mask` optionally `&`s out some of the bits on the file.
pub fn read_pointer_table( pub(super) fn read_pointer_table(
&mut self, &mut self,
offset: u64, offset: u64,
count: u64, count: u64,
@@ -213,7 +213,11 @@ impl QcowRawFile {
/// Reads a cluster's worth of 64 bit offsets and returns them as a vector. /// Reads a cluster's worth of 64 bit offsets and returns them as a vector.
/// `mask` optionally `&`s out some of the bits on the file. /// `mask` optionally `&`s out some of the bits on the file.
pub fn read_pointer_cluster(&mut self, offset: u64, mask: Option<u64>) -> io::Result<Vec<u64>> { pub(super) fn read_pointer_cluster(
&mut self,
offset: u64,
mask: Option<u64>,
) -> io::Result<Vec<u64>> {
let count = self.cluster_size / size_of::<u64>() as u64; let count = self.cluster_size / size_of::<u64>() as u64;
self.read_pointer_table(offset, count, mask) self.read_pointer_table(offset, count, mask)
} }
@@ -223,7 +227,7 @@ impl QcowRawFile {
/// ///
/// The callback may perform metadata I/O on this `QcowRawFile`, so all /// The callback may perform metadata I/O on this `QcowRawFile`, so all
/// entries are materialized before the final positional write. /// entries are materialized before the final positional write.
pub fn write_pointer_table<'a, T: Copy + 'a>( pub(super) fn write_pointer_table<'a, T: Copy + 'a>(
&mut self, &mut self,
offset: u64, offset: u64,
entries: impl Iterator<Item = &'a T>, entries: impl Iterator<Item = &'a T>,
@@ -240,7 +244,7 @@ impl QcowRawFile {
/// Writes a pointer table directly without transforming values. /// Writes a pointer table directly without transforming values.
/// ///
/// Uses the same materialize-then-write path as `write_pointer_table`. /// Uses the same materialize-then-write path as `write_pointer_table`.
pub fn write_pointer_table_direct<'a>( pub(super) fn write_pointer_table_direct<'a>(
&mut self, &mut self,
offset: u64, offset: u64,
entries: impl Iterator<Item = &'a u64>, entries: impl Iterator<Item = &'a u64>,
@@ -255,18 +259,21 @@ impl QcowRawFile {
/// Read a refcount block from the file and returns a Vec containing the block. /// Read a refcount block from the file and returns a Vec containing the block.
/// Always returns a cluster's worth of data. /// Always returns a cluster's worth of data.
#[inline] #[inline]
pub fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u64>> { pub(super) fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u64>> {
(self.read_refcount_fn)(&mut self.file, offset, self.refcount_block_entries as usize) (self.read_refcount_fn)(&mut self.file, offset, self.refcount_block_entries as usize)
} }
/// Writes a refcount block to the file. /// Writes a refcount block to the file.
#[inline] #[inline]
pub fn write_refcount_block(&mut self, offset: u64, table: &[u64]) -> io::Result<()> { pub(super) fn write_refcount_block(&mut self, offset: u64, table: &[u64]) -> io::Result<()> {
(self.write_refcount_fn)(&mut self.file, offset, table) (self.write_refcount_fn)(&mut self.file, offset, table)
} }
/// Allocates a new cluster at the end of the current file, return the address. /// Allocates a new cluster at the end of the current file, return the address.
pub fn add_cluster_end(&mut self, max_valid_cluster_offset: u64) -> io::Result<Option<u64>> { pub(super) fn add_cluster_end(
&mut self,
max_valid_cluster_offset: u64,
) -> io::Result<Option<u64>> {
// Determine where the new end of the file should be and set_len, which // Determine where the new end of the file should be and set_len, which
// translates to truncate(2). // translates to truncate(2).
let file_end: u64 = self.physical_size()?; let file_end: u64 = self.physical_size()?;
@@ -282,44 +289,44 @@ impl QcowRawFile {
} }
/// Returns a reference to the underlying file. /// Returns a reference to the underlying file.
pub fn file(&self) -> &AlignedFile { pub(super) fn file(&self) -> &AlignedFile {
&self.file &self.file
} }
/// Returns a mutable reference to the underlying file. /// Returns a mutable reference to the underlying file.
pub fn file_mut(&mut self) -> &mut AlignedFile { pub(super) fn file_mut(&mut self) -> &mut AlignedFile {
&mut self.file &mut self.file
} }
/// Returns the size of the file's clusters. /// Returns the size of the file's clusters.
pub fn cluster_size(&self) -> u64 { pub(super) fn cluster_size(&self) -> u64 {
self.cluster_size self.cluster_size
} }
/// Returns the offset of `address` within a cluster. /// Returns the offset of `address` within a cluster.
pub fn cluster_offset(&self, address: u64) -> u64 { pub(super) fn cluster_offset(&self, address: u64) -> u64 {
address & self.cluster_mask address & self.cluster_mask
} }
/// Returns the base address of the cluster containing `address`. /// Returns the base address of the cluster containing `address`.
pub fn cluster_address(&self, address: u64) -> u64 { pub(super) fn cluster_address(&self, address: u64) -> u64 {
address & !self.cluster_mask address & !self.cluster_mask
} }
/// Zeros out a cluster in the file. /// Zeros out a cluster in the file.
pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> { pub(super) fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
let cluster_size = self.cluster_size as usize; let cluster_size = self.cluster_size as usize;
self.file.write_all_zeroes_at(address, cluster_size)?; self.file.write_all_zeroes_at(address, cluster_size)?;
Ok(()) Ok(())
} }
/// Writes /// Writes
pub fn write_cluster(&mut self, address: u64, data: &[u8]) -> io::Result<()> { pub(super) fn write_cluster(&mut self, address: u64, data: &[u8]) -> io::Result<()> {
let cluster_size = self.cluster_size as usize; let cluster_size = self.cluster_size as usize;
self.file.write_all_at(&data[0..cluster_size], address) self.file.write_all_at(&data[0..cluster_size], address)
} }
pub fn physical_size(&self) -> io::Result<u64> { pub(super) fn physical_size(&self) -> io::Result<u64> {
self.file.metadata().map(|m| m.len()) self.file.metadata().map(|m| m.len())
} }
} }
@@ -41,11 +41,11 @@ pub enum Error {
}, },
} }
pub type Result<T> = result::Result<T, Error>; pub(super) type Result<T> = result::Result<T, Error>;
/// Represents the refcount entries for an open qcow file. /// Represents the refcount entries for an open qcow file.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RefCount { pub(super) struct RefCount {
ref_table: VecCache<u64>, ref_table: VecCache<u64>,
refcount_table_offset: u64, refcount_table_offset: u64,
refblock_cache: CacheMap<VecCache<u64>>, refblock_cache: CacheMap<VecCache<u64>>,
@@ -62,7 +62,7 @@ impl RefCount {
/// `refcount_block_entries` indicates the number of refcounts in each refcount block. /// `refcount_block_entries` indicates the number of refcounts in each refcount block.
/// `refcount_bits` is the number of bits per refcount (1, 2, 4, 8, 16, 32, or 64). /// `refcount_bits` is the number of bits per refcount (1, 2, 4, 8, 16, 32, or 64).
/// Each refcount table entry points to a refcount block. /// Each refcount table entry points to a refcount block.
pub fn new( pub(super) fn new(
raw_file: &mut QcowRawFile, raw_file: &mut QcowRawFile,
refcount_table_offset: u64, refcount_table_offset: u64,
refcount_table_entries: u64, refcount_table_entries: u64,
@@ -95,12 +95,12 @@ impl RefCount {
} }
/// Returns the number of refcounts per block. /// Returns the number of refcounts per block.
pub fn refcounts_per_block(&self) -> u64 { pub(super) fn refcounts_per_block(&self) -> u64 {
self.refcount_block_entries self.refcount_block_entries
} }
/// Returns the maximum valid cluster offset in the raw file for this refcount table. /// Returns the maximum valid cluster offset in the raw file for this refcount table.
pub fn max_valid_cluster_offset(&self) -> u64 { pub(super) fn max_valid_cluster_offset(&self) -> u64 {
self.max_valid_cluster_offset self.max_valid_cluster_offset
} }
@@ -109,7 +109,7 @@ impl RefCount {
/// allocate a cluster or read the required one and call this function again with the cluster. /// allocate a cluster or read the required one and call this function again with the cluster.
/// On success, an optional address of a dropped cluster is returned. The dropped cluster can /// On success, an optional address of a dropped cluster is returned. The dropped cluster can
/// be reused for other purposes. /// be reused for other purposes.
pub fn set_cluster_refcount( pub(super) fn set_cluster_refcount(
&mut self, &mut self,
raw_file: &mut QcowRawFile, raw_file: &mut QcowRawFile,
cluster_address: u64, cluster_address: u64,
@@ -167,7 +167,7 @@ impl RefCount {
/// Flush the dirty refcount blocks. This must be done before flushing the table that points to /// Flush the dirty refcount blocks. This must be done before flushing the table that points to
/// the blocks. /// the blocks.
pub fn flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()> { pub(super) fn flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()> {
// Write out all dirty L2 tables. // Write out all dirty L2 tables.
for (table_index, block) in self.refblock_cache.iter_mut().filter(|(_k, v)| v.dirty()) { for (table_index, block) in self.refblock_cache.iter_mut().filter(|(_k, v)| v.dirty()) {
let addr = self.ref_table[*table_index]; let addr = self.ref_table[*table_index];
@@ -183,7 +183,7 @@ impl RefCount {
/// Flush the refcount table that keeps the address of the refcounts blocks. /// Flush the refcount table that keeps the address of the refcounts blocks.
/// Returns true if the table changed since the previous `flush_table()` call. /// Returns true if the table changed since the previous `flush_table()` call.
pub fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> { pub(super) fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> {
if self.ref_table.dirty() { if self.ref_table.dirty() {
raw_file raw_file
.write_pointer_table_direct(self.refcount_table_offset, self.ref_table.iter())?; .write_pointer_table_direct(self.refcount_table_offset, self.ref_table.iter())?;
@@ -195,7 +195,7 @@ impl RefCount {
} }
/// Gets the refcount for a cluster with the given address. /// Gets the refcount for a cluster with the given address.
pub fn get_cluster_refcount( pub(super) fn get_cluster_refcount(
&mut self, &mut self,
raw_file: &mut QcowRawFile, raw_file: &mut QcowRawFile,
address: u64, address: u64,
@@ -12,21 +12,21 @@ use std::slice::SliceIndex;
/// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so /// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so
/// it can be checked if they need to be committed to disk. /// it can be checked if they need to be committed to disk.
pub trait Cacheable { pub(super) trait Cacheable {
/// Used to check if the item needs to be written out or if it can be discarded. /// Used to check if the item needs to be written out or if it can be discarded.
fn dirty(&self) -> bool; fn dirty(&self) -> bool;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// Represents a vector that implements the `Cacheable` trait so it can be held in a cache. /// Represents a vector that implements the `Cacheable` trait so it can be held in a cache.
pub struct VecCache<T: 'static + Copy + Default> { pub(super) struct VecCache<T: 'static + Copy + Default> {
vec: Box<[T]>, vec: Box<[T]>,
dirty: bool, dirty: bool,
} }
impl<T: 'static + Copy + Default> VecCache<T> { impl<T: 'static + Copy + Default> VecCache<T> {
/// Creates a `VecCache` that can hold `count` elements. /// Creates a `VecCache` that can hold `count` elements.
pub fn new(count: usize) -> VecCache<T> { pub(super) fn new(count: usize) -> VecCache<T> {
VecCache { VecCache {
vec: vec![Default::default(); count].into_boxed_slice(), vec: vec![Default::default(); count].into_boxed_slice(),
dirty: true, dirty: true,
@@ -34,14 +34,14 @@ impl<T: 'static + Copy + Default> VecCache<T> {
} }
/// Creates a `VecCache` from the passed in `vec`. /// Creates a `VecCache` from the passed in `vec`.
pub fn from_vec(vec: Vec<T>) -> VecCache<T> { pub(super) fn from_vec(vec: Vec<T>) -> VecCache<T> {
VecCache { VecCache {
vec: vec.into_boxed_slice(), vec: vec.into_boxed_slice(),
dirty: false, dirty: false,
} }
} }
pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output> pub(super) fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output>
where where
I: SliceIndex<[T]>, I: SliceIndex<[T]>,
{ {
@@ -49,17 +49,17 @@ impl<T: 'static + Copy + Default> VecCache<T> {
} }
/// Gets a reference to the underlying vector. /// Gets a reference to the underlying vector.
pub fn get_values(&self) -> &[T] { pub(super) fn get_values(&self) -> &[T] {
&self.vec &self.vec
} }
/// Mark this cache element as clean. /// Mark this cache element as clean.
pub fn mark_clean(&mut self) { pub(super) fn mark_clean(&mut self) {
self.dirty = false; self.dirty = false;
} }
/// Returns the number of elements in the vector. /// Returns the number of elements in the vector.
pub fn len(&self) -> usize { pub(super) fn len(&self) -> usize {
self.vec.len() self.vec.len()
} }
@@ -68,7 +68,7 @@ impl<T: 'static + Copy + Default> VecCache<T> {
/// No-op if `new_len <= self.len()`. Allocates a new buffer, copies /// No-op if `new_len <= self.len()`. Allocates a new buffer, copies
/// existing data, and fills new elements with default values. /// existing data, and fills new elements with default values.
/// Marks the cache as dirty. /// Marks the cache as dirty.
pub fn extend(&mut self, new_len: usize) { pub(super) fn extend(&mut self, new_len: usize) {
if new_len <= self.vec.len() { if new_len <= self.vec.len() {
return; return;
} }
@@ -109,37 +109,37 @@ impl<T: 'static + Copy + Default> Deref for VecCache<T> {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CacheMap<T: Cacheable> { pub(super) struct CacheMap<T: Cacheable> {
capacity: usize, capacity: usize,
map: HashMap<usize, T>, map: HashMap<usize, T>,
} }
impl<T: Cacheable> CacheMap<T> { impl<T: Cacheable> CacheMap<T> {
pub fn new(capacity: usize) -> Self { pub(super) fn new(capacity: usize) -> Self {
CacheMap { CacheMap {
capacity, capacity,
map: HashMap::with_capacity(capacity), map: HashMap::with_capacity(capacity),
} }
} }
pub fn contains_key(&self, key: usize) -> bool { pub(super) fn contains_key(&self, key: usize) -> bool {
self.map.contains_key(&key) self.map.contains_key(&key)
} }
pub fn get(&self, index: usize) -> Option<&T> { pub(super) fn get(&self, index: usize) -> Option<&T> {
self.map.get(&index) self.map.get(&index)
} }
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { pub(super) fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.map.get_mut(&index) self.map.get_mut(&index)
} }
pub fn iter_mut(&mut self) -> IterMut<'_, usize, T> { pub(super) fn iter_mut(&mut self) -> IterMut<'_, usize, T> {
self.map.iter_mut() self.map.iter_mut()
} }
// Check if the refblock cache is full and we need to evict. // Check if the refblock cache is full and we need to evict.
pub fn insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()> pub(super) fn insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()>
where where
F: FnOnce(usize, T) -> io::Result<()>, F: FnOnce(usize, T) -> io::Result<()>,
{ {
-9
View File
@@ -1,9 +0,0 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "io_uring")]
pub(crate) mod async_uring;
pub(crate) mod sync;
pub(crate) use super::{common, internal};
+1 -1
View File
@@ -28,7 +28,7 @@ use std::str::FromStr;
use std::{cmp, io, mem, result}; use std::{cmp, io, mem, result};
pub use aligned_file::AlignedFile; pub use aligned_file::AlignedFile;
use formats::qcow::internal as qcow; use formats::qcow;
#[cfg(feature = "io_uring")] #[cfg(feature = "io_uring")]
use io_uring::{IoUring, Probe, opcode}; use io_uring::{IoUring, Probe, opcode};
use libc::{ use libc::{
+1 -1
View File
@@ -14,7 +14,7 @@ use std::sync::mpsc::Receiver;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::{cmp, fs, io, panic, thread}; use std::{cmp, fs, io, panic, thread};
use block::formats::qcow::internal::ImageType as QcowImageType; use block::formats::qcow::ImageType as QcowImageType;
use test_infra::*; use test_infra::*;
use vmm_sys_util::tempdir::TempDir; use vmm_sys_util::tempdir::TempDir;
#[cfg(not(feature = "mshv"))] #[cfg(not(feature = "mshv"))]
+1 -2
View File
@@ -15,8 +15,7 @@ use std::thread;
use std::time::Duration; use std::time::Duration;
use block::async_io::{AsyncIo, GuestMemoryTarget}; use block::async_io::{AsyncIo, GuestMemoryTarget};
use block::formats::qcow::internal::{BackingFileConfig, ImageType}; use block::formats::qcow::{BackingFileConfig, ImageType, QcowDisk, QcowTempDisk};
use block::formats::qcow::{QcowDisk, QcowTempDisk};
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap}; use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::tempfile::TempFile; use vmm_sys_util::tempfile::TempFile;