block: qcow: Add unified QcowDisk wrapper

Introduce QcowDisk, a unified DiskFile implementation for QCOW2
disk images that handles backend selection at runtime via a
use_io_uring flag, matching the pattern used by FixedVhdDisk.

The wrapper delegates to QcowSync or QcowAsync based on the flag
and includes a compile time guard that returns an error when
io_uring is requested but the feature is not enabled.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2026-04-23 16:40:49 +02:00
committed by Rob Bradford
parent b59501e041
commit 27ee36449c
4 changed files with 191 additions and 2 deletions

View File

@@ -23,6 +23,7 @@ pub mod qcow;
#[cfg(feature = "io_uring")]
pub mod qcow_async;
pub(crate) mod qcow_common;
pub mod qcow_disk;
pub mod qcow_sync;
#[cfg(feature = "io_uring")]
pub(crate) mod raw_async;

View File

@@ -186,7 +186,7 @@ pub struct QcowAsync {
}
impl QcowAsync {
fn new(
pub(crate) fn new(
metadata: Arc<QcowMetadata>,
data_file: QcowRawFile,
backing_file: Option<Arc<dyn BackingRead>>,

188
block/src/qcow_disk.rs Normal file
View File

@@ -0,0 +1,188 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use std::{fmt, io};
use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError};
use crate::disk_file;
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
use crate::qcow::backing::shared_backing_from;
use crate::qcow::metadata::{BackingRead, QcowMetadata};
use crate::qcow::qcow_raw_file::QcowRawFile;
use crate::qcow::{MAX_NESTING_DEPTH, RawFile, parse_qcow};
#[cfg(feature = "io_uring")]
use crate::qcow_async::QcowAsync;
use crate::qcow_sync::QcowSync;
/// Unified DiskFile wrapper for QCOW2 disk images.
///
/// Holds the in memory QCOW2 metadata, the data file, and an optional
/// backing file. The metadata is wrapped in an `Arc` because
/// [`QcowSync`] and [`QcowAsync`] I/O workers receive a clone when
/// they are created via [`create_async_io`](DiskFile::create_async_io).
/// The backing file is likewise shared with workers through an `Arc`.
///
/// The `sparse` flag controls whether the image advertises discard
/// support to the guest. The `use_io_uring` flag selects between the
/// [`QcowSync`] and [`QcowAsync`] I/O backends. Both are recorded at
/// construction time and propagated through [`try_clone`](DiskFile::try_clone).
pub struct QcowDisk {
metadata: Arc<QcowMetadata>,
backing_file: Option<Arc<dyn BackingRead>>,
sparse: bool,
data_raw_file: QcowRawFile,
use_io_uring: bool,
}
impl fmt::Debug for QcowDisk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("QcowDisk")
.field("sparse", &self.sparse)
.field("has_backing", &self.backing_file.is_some())
.field("use_io_uring", &self.use_io_uring)
.finish_non_exhaustive()
}
}
impl QcowDisk {
pub fn new(
file: File,
direct_io: bool,
backing_files: bool,
sparse: bool,
use_io_uring: bool,
) -> BlockResult<Self> {
#[cfg(not(feature = "io_uring"))]
if use_io_uring {
return Err(BlockError::new(
BlockErrorKind::UnsupportedFeature,
DiskFileError::NewAsyncIo(io::Error::other(
"io_uring requested but feature is not enabled",
)),
));
}
let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 };
let raw_file = RawFile::new(file, direct_io);
let (inner, backing_file, sparse) = parse_qcow(raw_file, max_nesting_depth, sparse)
.map_err(|e| {
let e = if !backing_files && matches!(e.kind(), BlockErrorKind::Overflow) {
e.with_kind(BlockErrorKind::UnsupportedFeature)
} else {
e
};
e.with_op(ErrorOp::Open)
})?;
let data_raw_file = inner.raw_file.clone();
Ok(QcowDisk {
metadata: Arc::new(QcowMetadata::new(inner)),
backing_file: backing_file.map(shared_backing_from).transpose()?,
sparse,
data_raw_file,
use_io_uring,
})
}
}
impl Drop for QcowDisk {
fn drop(&mut self) {
self.metadata.shutdown();
}
}
impl disk_file::DiskSize for QcowDisk {
fn logical_size(&self) -> BlockResult<u64> {
Ok(self.metadata.virtual_size())
}
}
impl disk_file::PhysicalSize for QcowDisk {
fn physical_size(&self) -> BlockResult<u64> {
Ok(self.data_raw_file.physical_size()?)
}
}
impl disk_file::DiskFd for QcowDisk {
fn fd(&self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.data_raw_file.as_raw_fd())
}
}
impl disk_file::Geometry for QcowDisk {}
impl disk_file::SparseCapable for QcowDisk {
fn supports_sparse_operations(&self) -> bool {
true
}
fn supports_zero_flag(&self) -> bool {
true
}
}
impl disk_file::Resizable for QcowDisk {
fn resize(&mut self, size: u64) -> BlockResult<()> {
if self.backing_file.is_some() {
return Err(BlockError::new(
BlockErrorKind::UnsupportedFeature,
DiskFileError::ResizeError(io::Error::other(
"resize not supported with backing files",
)),
)
.with_op(ErrorOp::Resize));
}
self.metadata.resize(size).map_err(|e| {
BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e))
.with_op(ErrorOp::Resize)
})
}
}
impl disk_file::DiskFile for QcowDisk {}
impl disk_file::AsyncDiskFile for QcowDisk {
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
Ok(Box::new(QcowDisk {
metadata: Arc::clone(&self.metadata),
backing_file: self.backing_file.as_ref().map(Arc::clone),
sparse: self.sparse,
data_raw_file: self.data_raw_file.clone(),
use_io_uring: self.use_io_uring,
}))
}
fn create_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
if self.use_io_uring {
#[cfg(feature = "io_uring")]
{
return Ok(Box::new(
QcowAsync::new(
Arc::clone(&self.metadata),
self.data_raw_file.clone(),
self.backing_file.as_ref().map(Arc::clone),
self.sparse,
ring_depth,
)
.map_err(|e| {
BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e))
})?,
));
}
#[cfg(not(feature = "io_uring"))]
unreachable!("use_io_uring is set but io_uring feature is not enabled");
}
let _ = ring_depth;
Ok(Box::new(QcowSync::new(
Arc::clone(&self.metadata),
self.data_raw_file.clone(),
self.backing_file.as_ref().map(Arc::clone),
self.sparse,
)))
}
}

View File

@@ -165,7 +165,7 @@ pub struct QcowSync {
}
impl QcowSync {
fn new(
pub(crate) fn new(
metadata: Arc<QcowMetadata>,
data_file: QcowRawFile,
backing_file: Option<Arc<dyn BackingRead>>,