block: qcow: Port backing file support to FileExt

Replace use of raw pread64/pwrite64 functions with
std::os::unix::fs::FileExt for I/O without a cursor.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
This commit is contained in:
Rob Bradford
2026-06-09 17:07:39 +01:00
parent 30e79de26f
commit 2e2167368e
+18 -19
View File
@@ -6,23 +6,25 @@
//! Thread safe backing file readers for QCOW2 images. //! Thread safe backing file readers for QCOW2 images.
use std::fs::File;
use std::io; use std::io;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}; use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
use std::os::unix::fs::FileExt;
use std::sync::Arc; 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::{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, pread_alloc, pread_exact}; use crate::formats::qcow::common::decompress_cluster;
/// Raw backing file using pread64 on a duplicated fd. /// Raw backing file using position-independent reads on a duplicated fd.
pub(crate) struct RawBacking { pub(crate) struct RawBacking {
pub(crate) fd: OwnedFd, pub(crate) file: File,
pub(crate) virtual_size: u64, pub(crate) virtual_size: u64,
} }
// SAFETY: The only I/O operation is pread64 which is position independent // SAFETY: The only I/O operation is read_at which is position independent
// and safe for concurrent use from multiple threads. // and safe for concurrent use from multiple threads.
unsafe impl Sync for RawBacking {} unsafe impl Sync for RawBacking {}
@@ -34,9 +36,9 @@ impl BackingRead for RawBacking {
} }
let available = (self.virtual_size - address) as usize; let available = (self.virtual_size - address) as usize;
if available >= buf.len() { if available >= buf.len() {
pread_exact(self.fd.as_raw_fd(), buf, address) self.file.read_exact_at(buf, address)
} else { } else {
pread_exact(self.fd.as_raw_fd(), &mut buf[..available], address)?; self.file.read_exact_at(&mut buf[..available], address)?;
buf[available..].fill(0); buf[available..].fill(0);
Ok(()) Ok(())
} }
@@ -51,14 +53,14 @@ impl BackingRead for RawBacking {
/// are handled recursively via the optional `backing_file` field. /// are handled recursively via the optional `backing_file` field.
pub(crate) struct Qcow2Backing { pub(crate) struct Qcow2Backing {
pub(crate) metadata: Arc<QcowMetadata>, pub(crate) metadata: Arc<QcowMetadata>,
pub(crate) data_fd: OwnedFd, pub(crate) data_file: File,
pub(crate) backing_file: Option<Arc<dyn BackingRead>>, pub(crate) backing_file: Option<Arc<dyn BackingRead>>,
pub(crate) cluster_size: u64, pub(crate) cluster_size: u64,
pub(crate) decoder: Arc<dyn Decoder>, pub(crate) decoder: Arc<dyn Decoder>,
} }
// SAFETY: All reads go through QcowMetadata which uses RwLock // SAFETY: All reads go through QcowMetadata which uses RwLock
// and pread64 which is position independent and thread safe. // and read_exact_at which is position independent and thread safe.
unsafe impl Sync for Qcow2Backing {} unsafe impl Sync for Qcow2Backing {}
impl BackingRead for Qcow2Backing { impl BackingRead for Qcow2Backing {
@@ -79,8 +81,6 @@ impl BackingRead for Qcow2Backing {
} }
impl Qcow2Backing { impl Qcow2Backing {
/// Resolve cluster mappings via metadata then read allocated clusters
/// with pread64.
fn read_clusters(&self, address: u64, buf: &mut [u8]) -> io::Result<()> { fn read_clusters(&self, address: u64, buf: &mut [u8]) -> io::Result<()> {
let total_len = buf.len(); let total_len = buf.len();
let has_backing = self.backing_file.is_some(); let has_backing = self.backing_file.is_some();
@@ -100,8 +100,7 @@ impl Qcow2Backing {
offset: host_offset, offset: host_offset,
length, length,
} => { } => {
pread_exact( self.data_file.read_exact_at(
self.data_fd.as_raw_fd(),
&mut buf[buf_offset..buf_offset + length as usize], &mut buf[buf_offset..buf_offset + length as usize],
host_offset, host_offset,
)?; )?;
@@ -113,8 +112,8 @@ impl Qcow2Backing {
cluster_offset, cluster_offset,
length, length,
} => { } => {
let compressed = let mut compressed = vec![0u8; compressed_size];
pread_alloc(self.data_fd.as_raw_fd(), host_offset, compressed_size)?; self.data_file.read_exact_at(&mut compressed, host_offset)?;
let decompressed = decompress_cluster( let decompressed = decompress_cluster(
&compressed, &compressed,
self.cluster_size as usize, self.cluster_size as usize,
@@ -162,17 +161,17 @@ pub fn shared_backing_from(bf: BackingFile) -> BlockResult<Arc<dyn BackingRead>>
match kind { match kind {
BackingKind::Raw(raw_file) => { BackingKind::Raw(raw_file) => {
let fd = dup_fd(raw_file.as_fd())?; let file = File::from(dup_fd(raw_file.as_fd())?);
Ok(Arc::new(RawBacking { fd, virtual_size })) Ok(Arc::new(RawBacking { file, virtual_size }))
} }
BackingKind::Qcow { inner, backing } => { BackingKind::Qcow { inner, backing } => {
let data_fd = dup_fd(inner.raw_file.as_fd())?; let data_file = File::from(dup_fd(inner.raw_file.as_fd())?);
let metadata = Arc::new(QcowMetadata::new(*inner)); let metadata = Arc::new(QcowMetadata::new(*inner));
Ok(Arc::new(Qcow2Backing { Ok(Arc::new(Qcow2Backing {
cluster_size: metadata.cluster_size(), cluster_size: metadata.cluster_size(),
decoder: metadata.decoder(), decoder: metadata.decoder(),
metadata, metadata,
data_fd, data_file,
backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?, backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?,
})) }))
} }