From 201ddaef55a01bff90e8689194d9e775c65cfffd Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 9 Jun 2026 23:14:38 +0100 Subject: [PATCH] block: qcow: Port to FileExt Replace the use of the pread64/pwrite64 helpers with versions from std::os::unix::fs::FileExt. As this was the last use of these pread functions remove them and their tests. Assisted-by: Claude:Opus-4.6 Signed-off-by: Rob Bradford --- block/src/formats/qcow/common.rs | 95 +------------------- block/src/formats/qcow/worker/async_uring.rs | 19 ++-- block/src/formats/qcow/worker/sync.rs | 20 +++-- 3 files changed, 27 insertions(+), 107 deletions(-) diff --git a/block/src/formats/qcow/common.rs b/block/src/formats/qcow/common.rs index 74a506629..6253ff11f 100644 --- a/block/src/formats/qcow/common.rs +++ b/block/src/formats/qcow/common.rs @@ -7,53 +7,13 @@ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause //! Shared helpers for QCOW2 sync and async backends. -//! -//! Position-independent I/O helpers used by both `qcow_sync` and `qcow_async`. use std::io; -use std::os::fd::RawFd; #[cfg(test)] use super::internal; use super::internal::decoder::Decoder; -// -- Position independent I/O helpers -- -// -// Duplicated file descriptors share the kernel file description and thus the -// file position. Using seek then read from multiple queues races on that -// shared position. pread64 and pwrite64 are atomic and never touch the position. - -/// Read exactly the requested bytes at offset, looping on short reads. -pub fn pread_exact(fd: RawFd, buf: &mut [u8], offset: u64) -> io::Result<()> { - let mut total = 0usize; - while total < buf.len() { - // SAFETY: buf and fd are valid for the lifetime of the call. - let ret = unsafe { - libc::pread64( - fd, - buf[total..].as_mut_ptr().cast(), - buf.len() - total, - (offset + total as u64) as libc::off_t, - ) - }; - if ret < 0 { - return Err(io::Error::last_os_error()); - } - if ret == 0 { - return Err(io::Error::from(io::ErrorKind::UnexpectedEof)); - } - total += ret as usize; - } - Ok(()) -} - -/// Allocate a buffer and pread exactly `len` bytes at `offset`. -pub fn pread_alloc(fd: RawFd, offset: u64, len: usize) -> io::Result> { - let mut buf = vec![0u8; len]; - pread_exact(fd, &mut buf, offset)?; - Ok(buf) -} - /// Decompress a full QCOW2 cluster from compressed data. /// /// Returns a `cluster_size` byte buffer with the decompressed cluster @@ -74,44 +34,17 @@ pub fn decompress_cluster( Ok(decompressed) } -/// Write all bytes to fd at offset, looping on short writes. -pub fn pwrite_all(fd: RawFd, buf: &[u8], offset: u64) -> io::Result<()> { - let mut total = 0usize; - while total < buf.len() { - // SAFETY: buf and fd are valid for the lifetime of the call. - let ret = unsafe { - libc::pwrite64( - fd, - buf[total..].as_ptr().cast(), - buf.len() - total, - (offset + total as u64) as libc::off_t, - ) - }; - if ret < 0 { - return Err(io::Error::last_os_error()); - } - if ret == 0 { - return Err(io::Error::other("pwrite64 wrote 0 bytes")); - } - total += ret as usize; - } - Ok(()) -} - #[cfg(test)] pub(crate) mod unit_tests { use std::fs::File; use std::io::{Read, Seek, SeekFrom, Write}; - use std::os::unix::fs::FileExt; - use std::os::unix::io::AsRawFd; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use flate2::Compression; use flate2::write::DeflateEncoder; - use vmm_sys_util::tempfile::TempFile; + use super::decompress_cluster; use super::internal::decoder::ZlibDecoder; - use super::{decompress_cluster, pread_alloc}; const COMPRESSED_FLAG: u64 = 1 << 62; const CLUSTER_USED_FLAG: u64 = 1 << 63; @@ -134,10 +67,6 @@ pub(crate) mod unit_tests { } /// Compress every allocated cluster in a QCOW2 image file in place. - /// - /// Walks L1 -> L2 tables, compresses each standard cluster with raw - /// deflate, appends the compressed payload at the end of the file, - /// and rewrites the L2 entry with the compressed layout. pub fn compress_allocated_clusters(file: &mut File) { file.seek(SeekFrom::Start(HEADER_CLUSTER_BITS_OFFSET)) .unwrap(); @@ -190,12 +119,6 @@ pub(crate) mod unit_tests { file.seek(SeekFrom::Start(append_offset)).unwrap(); file.write_all(&compressed).unwrap(); - // The L2 entry encodes the compressed size in units of - // 512 byte sectors. The reader decodes the sector count - // back and computes: nsectors * 512 - (addr & 511). - // Because addr is 512 aligned, this yields nsectors * 512 - // which rounds up to the next sector boundary. The file - // must contain enough bytes for that rounded up pread. let padded_len = (compressed.len() + 511) & !511; if padded_len > compressed.len() { let padding = vec![0u8; padded_len - compressed.len()]; @@ -214,22 +137,6 @@ pub(crate) mod unit_tests { file.flush().unwrap(); } - #[test] - fn test_pread_alloc() { - let temp = TempFile::new().unwrap(); - let file = temp.as_file(); - let data: Vec = (0..=255).cycle().take(4096).collect(); - file.write_all_at(&data, 0).unwrap(); - - let buf = pread_alloc(file.as_raw_fd(), 0, 4096).unwrap(); - assert_eq!(buf, data); - - let buf = pread_alloc(file.as_raw_fd(), 100, 200).unwrap(); - assert_eq!(buf, &data[100..300]); - - pread_alloc(file.as_raw_fd(), 4000, 200).unwrap_err(); - } - #[test] fn test_decompress_cluster() { let cluster_size = 65536; diff --git a/block/src/formats/qcow/worker/async_uring.rs b/block/src/formats/qcow/worker/async_uring.rs index 4844aa8da..4e9d06d3b 100644 --- a/block/src/formats/qcow/worker/async_uring.rs +++ b/block/src/formats/qcow/worker/async_uring.rs @@ -10,13 +10,14 @@ use std::cmp::{max, min}; use std::io; +use std::os::unix::fs::FileExt; use std::os::unix::io::AsRawFd; use std::sync::Arc; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; -use super::common::{decompress_cluster, pread_alloc, pread_exact, pwrite_all}; +use super::common::decompress_cluster; use super::internal::decoder::Decoder; use super::internal::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, @@ -399,7 +400,9 @@ impl QcowAsync { .map_err(AsyncIoError::ReadVectored)?; } else { let mut buf = vec![0u8; len]; - pread_exact(data_file.as_raw_fd(), &mut buf, host_offset) + data_file + .file() + .read_exact_at(&mut buf, host_offset) .map_err(AsyncIoError::ReadVectored)?; op.write_bytes_at(buf_offset, &buf) .map_err(AsyncIoError::ReadVectored)?; @@ -412,9 +415,11 @@ impl QcowAsync { cluster_offset, length, } => { - let compressed = - pread_alloc(data_file.as_raw_fd(), host_offset, compressed_size) - .map_err(AsyncIoError::ReadVectored)?; + let mut compressed = vec![0u8; compressed_size]; + data_file + .file() + .read_exact_at(&mut compressed, host_offset) + .map_err(AsyncIoError::ReadVectored)?; let decompressed = decompress_cluster(&compressed, cluster_size as usize, decoder) .map_err(AsyncIoError::ReadVectored)?; @@ -498,7 +503,9 @@ impl QcowAsync { let mut buf = vec![0u8; count]; op.read_bytes_at(buf_offset, &mut buf) .map_err(AsyncIoError::WriteVectored)?; - pwrite_all(data_file.as_raw_fd(), &buf, host_offset) + data_file + .file() + .write_all_at(&buf, host_offset) .map_err(AsyncIoError::WriteVectored)?; } } diff --git a/block/src/formats/qcow/worker/sync.rs b/block/src/formats/qcow/worker/sync.rs index ac60726de..190b1bf65 100644 --- a/block/src/formats/qcow/worker/sync.rs +++ b/block/src/formats/qcow/worker/sync.rs @@ -6,13 +6,13 @@ use std::cmp::min; use std::collections::VecDeque; -use std::os::unix::io::AsRawFd; +use std::os::unix::fs::FileExt; use std::sync::Arc; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt}; -use super::common::{decompress_cluster, pread_alloc, pread_exact, pwrite_all}; +use super::common::decompress_cluster; use super::internal::decoder::Decoder; use super::internal::metadata::{ BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, @@ -109,7 +109,9 @@ impl QcowSync { .map_err(AsyncIoError::ReadVectored)?; } else { let mut buf = vec![0u8; len]; - pread_exact(self.data_file.as_raw_fd(), &mut buf, host_offset) + self.data_file + .file() + .read_exact_at(&mut buf, host_offset) .map_err(AsyncIoError::ReadVectored)?; op.write_bytes_at(buf_offset, &buf) .map_err(AsyncIoError::ReadVectored)?; @@ -122,9 +124,11 @@ impl QcowSync { cluster_offset, length, } => { - let compressed = - pread_alloc(self.data_file.as_raw_fd(), host_offset, compressed_size) - .map_err(AsyncIoError::ReadVectored)?; + let mut compressed = vec![0u8; compressed_size]; + self.data_file + .file() + .read_exact_at(&mut compressed, host_offset) + .map_err(AsyncIoError::ReadVectored)?; let decompressed = decompress_cluster(&compressed, self.cluster_size as usize, &*self.decoder) .map_err(AsyncIoError::ReadVectored)?; @@ -205,7 +209,9 @@ impl QcowSync { let mut buf = vec![0u8; count]; op.read_bytes_at(buf_offset, &mut buf) .map_err(AsyncIoError::WriteVectored)?; - pwrite_all(self.data_file.as_raw_fd(), &buf, host_offset) + self.data_file + .file() + .write_all_at(&buf, host_offset) .map_err(AsyncIoError::WriteVectored)?; } }