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 <rbradford@meta.com>
This commit is contained in:
Rob Bradford
2026-06-09 23:14:38 +01:00
parent 108d251c1d
commit 201ddaef55
3 changed files with 27 additions and 107 deletions
+1 -94
View File
@@ -7,53 +7,13 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//! Shared helpers for QCOW2 sync and async backends. //! 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::io;
use std::os::fd::RawFd;
#[cfg(test)] #[cfg(test)]
use super::internal; use super::internal;
use super::internal::decoder::Decoder; 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<Vec<u8>> {
let mut buf = vec![0u8; len];
pread_exact(fd, &mut buf, offset)?;
Ok(buf)
}
/// 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
@@ -74,44 +34,17 @@ pub fn decompress_cluster(
Ok(decompressed) 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)] #[cfg(test)]
pub(crate) mod unit_tests { pub(crate) mod unit_tests {
use std::fs::File; use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write}; 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 byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use flate2::Compression; use flate2::Compression;
use flate2::write::DeflateEncoder; use flate2::write::DeflateEncoder;
use vmm_sys_util::tempfile::TempFile;
use super::decompress_cluster;
use super::internal::decoder::ZlibDecoder; use super::internal::decoder::ZlibDecoder;
use super::{decompress_cluster, pread_alloc};
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;
@@ -134,10 +67,6 @@ pub(crate) mod unit_tests {
} }
/// Compress every allocated cluster in a QCOW2 image file in place. /// 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) { pub fn compress_allocated_clusters(file: &mut File) {
file.seek(SeekFrom::Start(HEADER_CLUSTER_BITS_OFFSET)) file.seek(SeekFrom::Start(HEADER_CLUSTER_BITS_OFFSET))
.unwrap(); .unwrap();
@@ -190,12 +119,6 @@ pub(crate) mod unit_tests {
file.seek(SeekFrom::Start(append_offset)).unwrap(); file.seek(SeekFrom::Start(append_offset)).unwrap();
file.write_all(&compressed).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; let padded_len = (compressed.len() + 511) & !511;
if padded_len > compressed.len() { if padded_len > compressed.len() {
let padding = vec![0u8; padded_len - compressed.len()]; let padding = vec![0u8; padded_len - compressed.len()];
@@ -214,22 +137,6 @@ pub(crate) mod unit_tests {
file.flush().unwrap(); file.flush().unwrap();
} }
#[test]
fn test_pread_alloc() {
let temp = TempFile::new().unwrap();
let file = temp.as_file();
let data: Vec<u8> = (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] #[test]
fn test_decompress_cluster() { fn test_decompress_cluster() {
let cluster_size = 65536; let cluster_size = 65536;
+13 -6
View File
@@ -10,13 +10,14 @@
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::io; use std::io;
use std::os::unix::fs::FileExt;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::sync::Arc; use std::sync::Arc;
use vmm_sys_util::eventfd::EventFd; 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, pread_alloc, pread_exact, pwrite_all}; use super::common::decompress_cluster;
use super::internal::decoder::Decoder; use super::internal::decoder::Decoder;
use super::internal::metadata::{ use super::internal::metadata::{
BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata,
@@ -399,7 +400,9 @@ impl QcowAsync {
.map_err(AsyncIoError::ReadVectored)?; .map_err(AsyncIoError::ReadVectored)?;
} else { } else {
let mut buf = vec![0u8; len]; 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)?; .map_err(AsyncIoError::ReadVectored)?;
op.write_bytes_at(buf_offset, &buf) op.write_bytes_at(buf_offset, &buf)
.map_err(AsyncIoError::ReadVectored)?; .map_err(AsyncIoError::ReadVectored)?;
@@ -412,9 +415,11 @@ impl QcowAsync {
cluster_offset, cluster_offset,
length, length,
} => { } => {
let compressed = let mut compressed = vec![0u8; compressed_size];
pread_alloc(data_file.as_raw_fd(), host_offset, compressed_size) data_file
.map_err(AsyncIoError::ReadVectored)?; .file()
.read_exact_at(&mut compressed, host_offset)
.map_err(AsyncIoError::ReadVectored)?;
let decompressed = let decompressed =
decompress_cluster(&compressed, cluster_size as usize, decoder) decompress_cluster(&compressed, cluster_size as usize, decoder)
.map_err(AsyncIoError::ReadVectored)?; .map_err(AsyncIoError::ReadVectored)?;
@@ -498,7 +503,9 @@ impl QcowAsync {
let mut buf = vec![0u8; count]; let mut buf = vec![0u8; count];
op.read_bytes_at(buf_offset, &mut buf) op.read_bytes_at(buf_offset, &mut buf)
.map_err(AsyncIoError::WriteVectored)?; .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)?; .map_err(AsyncIoError::WriteVectored)?;
} }
} }
+13 -7
View File
@@ -6,13 +6,13 @@
use std::cmp::min; use std::cmp::min;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::os::unix::io::AsRawFd; use std::os::unix::fs::FileExt;
use std::sync::Arc; use std::sync::Arc;
use vmm_sys_util::eventfd::EventFd; 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, pread_alloc, pread_exact, pwrite_all}; use super::common::decompress_cluster;
use super::internal::decoder::Decoder; use super::internal::decoder::Decoder;
use super::internal::metadata::{ use super::internal::metadata::{
BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata, BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata,
@@ -109,7 +109,9 @@ impl QcowSync {
.map_err(AsyncIoError::ReadVectored)?; .map_err(AsyncIoError::ReadVectored)?;
} else { } else {
let mut buf = vec![0u8; len]; 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)?; .map_err(AsyncIoError::ReadVectored)?;
op.write_bytes_at(buf_offset, &buf) op.write_bytes_at(buf_offset, &buf)
.map_err(AsyncIoError::ReadVectored)?; .map_err(AsyncIoError::ReadVectored)?;
@@ -122,9 +124,11 @@ impl QcowSync {
cluster_offset, cluster_offset,
length, length,
} => { } => {
let compressed = let mut compressed = vec![0u8; compressed_size];
pread_alloc(self.data_file.as_raw_fd(), host_offset, compressed_size) self.data_file
.map_err(AsyncIoError::ReadVectored)?; .file()
.read_exact_at(&mut compressed, host_offset)
.map_err(AsyncIoError::ReadVectored)?;
let decompressed = let decompressed =
decompress_cluster(&compressed, self.cluster_size as usize, &*self.decoder) decompress_cluster(&compressed, self.cluster_size as usize, &*self.decoder)
.map_err(AsyncIoError::ReadVectored)?; .map_err(AsyncIoError::ReadVectored)?;
@@ -205,7 +209,9 @@ impl QcowSync {
let mut buf = vec![0u8; count]; let mut buf = vec![0u8; count];
op.read_bytes_at(buf_offset, &mut buf) op.read_bytes_at(buf_offset, &mut buf)
.map_err(AsyncIoError::WriteVectored)?; .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)?; .map_err(AsyncIoError::WriteVectored)?;
} }
} }