From 5ec80d45ab5fb609678443ced7c6030166bb1f3a Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Fri, 20 Mar 2026 16:07:51 +0100 Subject: [PATCH] block: qcow: Move Qcow2MetadataBacking to qcow/backing Move the QCOW2 metadata backed reader into qcow/backing alongside RawBacking. Signed-off-by: Anatol Belski --- block/src/qcow/backing.rs | 92 ++++++++++++++++++++++++++++++++++++++- block/src/qcow_sync.rs | 89 +------------------------------------ 2 files changed, 92 insertions(+), 89 deletions(-) diff --git a/block/src/qcow/backing.rs b/block/src/qcow/backing.rs index 1ad1b74b9..c688900c5 100644 --- a/block/src/qcow/backing.rs +++ b/block/src/qcow/backing.rs @@ -8,8 +8,9 @@ use std::io; use std::os::fd::{AsRawFd, OwnedFd}; +use std::sync::Arc; -use crate::qcow::metadata::BackingRead; +use crate::qcow::metadata::{BackingRead, ClusterReadMapping, QcowMetadata}; use crate::qcow_common::pread_exact; /// Raw backing file using pread64 on a duplicated fd. @@ -38,3 +39,92 @@ impl BackingRead for RawBacking { } } } + +/// QCOW2 image used as a backing file for another QCOW2 image. +/// +/// Resolves guest offsets through the QCOW2 cluster mapping (L1/L2 +/// tables, refcounts) before reading the underlying data. Read only +/// because backing files never receive writes. Nested backing chains +/// are handled recursively via the optional `backing_file` field. +pub(crate) struct Qcow2MetadataBacking { + pub(crate) metadata: Arc, + pub(crate) data_fd: OwnedFd, + pub(crate) backing_file: Option>, +} + +// SAFETY: All reads go through QcowMetadata which uses RwLock +// and pread64 which is position independent and thread safe. +unsafe impl Sync for Qcow2MetadataBacking {} + +impl BackingRead for Qcow2MetadataBacking { + fn read_at(&self, address: u64, buf: &mut [u8]) -> io::Result<()> { + let virtual_size = self.metadata.virtual_size(); + if address >= virtual_size { + buf.fill(0); + return Ok(()); + } + let available = (virtual_size - address) as usize; + if available < buf.len() { + self.read_clusters(address, &mut buf[..available])?; + buf[available..].fill(0); + return Ok(()); + } + self.read_clusters(address, buf) + } +} + +impl Qcow2MetadataBacking { + /// Resolve cluster mappings via metadata then read allocated clusters + /// with pread64. + fn read_clusters(&self, address: u64, buf: &mut [u8]) -> io::Result<()> { + let total_len = buf.len(); + let has_backing = self.backing_file.is_some(); + + let mappings = self + .metadata + .map_clusters_for_read(address, total_len, has_backing)?; + + let mut buf_offset = 0usize; + for mapping in mappings { + match mapping { + ClusterReadMapping::Zero { length } => { + buf[buf_offset..buf_offset + length as usize].fill(0); + buf_offset += length as usize; + } + ClusterReadMapping::Allocated { + offset: host_offset, + length, + } => { + pread_exact( + self.data_fd.as_raw_fd(), + &mut buf[buf_offset..buf_offset + length as usize], + host_offset, + )?; + buf_offset += length as usize; + } + ClusterReadMapping::Compressed { data } => { + let len = data.len(); + buf[buf_offset..buf_offset + len].copy_from_slice(&data); + buf_offset += len; + } + ClusterReadMapping::Backing { + offset: backing_offset, + length, + } => { + self.backing_file.as_ref().unwrap().read_at( + backing_offset, + &mut buf[buf_offset..buf_offset + length as usize], + )?; + buf_offset += length as usize; + } + } + } + Ok(()) + } +} + +impl Drop for Qcow2MetadataBacking { + fn drop(&mut self) { + self.metadata.shutdown(); + } +} diff --git a/block/src/qcow_sync.rs b/block/src/qcow_sync.rs index 5665ad475..483db80f5 100644 --- a/block/src/qcow_sync.rs +++ b/block/src/qcow_sync.rs @@ -15,7 +15,7 @@ use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFileError}; use crate::disk_file; use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp}; -use crate::qcow::backing::RawBacking; +use crate::qcow::backing::{Qcow2MetadataBacking, RawBacking}; use crate::qcow::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, }; @@ -27,93 +27,6 @@ use crate::qcow_common::{ gather_from_iovecs, pread_exact, pwrite_all, scatter_to_iovecs, zero_fill_iovecs, }; -/// QCOW2 backing file with RwLock metadata and pread64 data reads. -/// -/// Read only because backing files never receive writes. Nested backing -/// files are handled recursively. -struct Qcow2MetadataBacking { - metadata: Arc, - data_fd: OwnedFd, - backing_file: Option>, -} - -// SAFETY: All reads go through QcowMetadata which uses RwLock -// and pread64 which is position independent and thread safe. -unsafe impl Sync for Qcow2MetadataBacking {} - -impl BackingRead for Qcow2MetadataBacking { - fn read_at(&self, address: u64, buf: &mut [u8]) -> io::Result<()> { - let virtual_size = self.metadata.virtual_size(); - if address >= virtual_size { - buf.fill(0); - return Ok(()); - } - let available = (virtual_size - address) as usize; - if available < buf.len() { - self.read_clusters(address, &mut buf[..available])?; - buf[available..].fill(0); - return Ok(()); - } - self.read_clusters(address, buf) - } -} - -impl Qcow2MetadataBacking { - /// Resolve cluster mappings via metadata then read allocated clusters - /// with pread64. - fn read_clusters(&self, address: u64, buf: &mut [u8]) -> io::Result<()> { - let total_len = buf.len(); - let has_backing = self.backing_file.is_some(); - - let mappings = self - .metadata - .map_clusters_for_read(address, total_len, has_backing)?; - - let mut buf_offset = 0usize; - for mapping in mappings { - match mapping { - ClusterReadMapping::Zero { length } => { - buf[buf_offset..buf_offset + length as usize].fill(0); - buf_offset += length as usize; - } - ClusterReadMapping::Allocated { - offset: host_offset, - length, - } => { - pread_exact( - self.data_fd.as_raw_fd(), - &mut buf[buf_offset..buf_offset + length as usize], - host_offset, - )?; - buf_offset += length as usize; - } - ClusterReadMapping::Compressed { data } => { - let len = data.len(); - buf[buf_offset..buf_offset + len].copy_from_slice(&data); - buf_offset += len; - } - ClusterReadMapping::Backing { - offset: backing_offset, - length, - } => { - self.backing_file.as_ref().unwrap().read_at( - backing_offset, - &mut buf[buf_offset..buf_offset + length as usize], - )?; - buf_offset += length as usize; - } - } - } - Ok(()) - } -} - -impl Drop for Qcow2MetadataBacking { - fn drop(&mut self) { - self.metadata.shutdown(); - } -} - /// Construct a thread safe backing file reader. fn shared_backing_from(bf: BackingFile) -> BlockResult> { let (kind, virtual_size) = bf.into_kind();