Delete QcowDiskSync and QcowDiskAsync wrapper structs along with
their DiskFile trait impls. Only the AsyncIo worker structs
QcowSync and QcowAsync remain. Reduce module visibility of
qcow_sync and qcow_async to pub(crate).
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Write a known data pattern through QcowFile, compress all clusters
in place, reopen and read back via the seek based file_read path.
This covers the decompress_l2_cluster code path used by QcowFile
which is separate from the pread based path in QcowSync/QcowAsync.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move decompression of compressed QCOW2 clusters out of the metadata
lock. Previously, reading a compressed cluster acquired a write lock
on metadata to perform in place decompression. Now, try_map_read
extracts the compressed layout (host offset, size) under a read lock
and returns it in the ClusterReadMapping::Compressed variant. Each
consumer (QcowSync, QcowAsync, Qcow2Backing, QcowFile) performs the
pread and decompression at the call site without holding any lock,
using the pread_alloc and decompress_cluster helpers.
Create the decoder once in QcowMetadata as Arc<dyn Decoder> and
share it via Arc::clone to QcowAsync, QcowSync, and Qcow2Backing
at construction time. This avoids per read RwLock acquisitions and
heap allocations. Add Send + Sync bounds to the Decoder trait.
This eliminates write lock contention on compressed reads, allowing
them to proceed concurrently with other read operations.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Cache the immutable cluster_size value at construction time in
QcowAsync, QcowSync, and Qcow2Backing. This avoids repeated RwLock
read acquisitions on the hot write and deallocation paths.
Replace QcowMetadata::cluster_offset() calls with inline bitmask
operations using the cached cluster_size. Remove the now unused
cluster_offset() method from QcowMetadata.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Add two reusable helpers for the compressed cluster read path:
- pread_alloc(fd, offset, len) allocates a buffer and fills it with
pread_exact, returning the owned Vec.
- decompress_cluster(compressed, cluster_size, decoder) allocates the
output buffer, decodes via the Decoder trait, and validates that the
decoder produced exactly cluster_size bytes.
These will be used by QcowSync, QcowAsync, Qcow2Backing, and the
legacy QcowFile.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Add pub fn alignment() to RawFile so that callers can
query the O_DIRECT buffer alignment requirement probed
at file open time.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
The old name read as 'metadata for a QCOW2 backing file' rather
than what it actually is: a QCOW2 backing file reader. Rename to
Qcow2Backing to parallel RawBacking and clarify intent.
Suggested-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move the backing file constructor into qcow/backing alongside the
types it creates. Both qcow_sync and qcow_async can now import
shared_backing_from directly from qcow/backing.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move the raw backing file reader into the new qcow/backing module
so it can be shared between qcow_sync and the upcoming qcow_async
backend.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Use query_device_size() instead of metadata().len() to correctly
handle block device and regular file handles.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch detect_image_type from qcow::Result to BlockResult with
explicit error classification at every I/O site. This is the last
function migrated before the From scaffolding can be removed.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch rebuild_refcounts from qcow::Result to BlockResult. The
inner helper functions remain on qcow::Result since they are purely
internal, and are wrapped with map_err at each call site where they
cross the BlockResult boundary.
InvalidRefcountTableSize errors are classified as CorruptImage since
they indicate inconsistent internal refcount structures rather than
a format violation.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch resize and grow_l1_table from qcow::Result to BlockResult.
All I/O error sites use explicit BlockError::new with the Io kind.
The write_to call in grow_l1_table rewraps WritingHeader as
ResizeIo to preserve the existing error semantics.
The two resize tests that check for ShrinkNotSupported and
ResizeWithBackingFile are updated to match on BlockErrorKind with
downcast to inspect the underlying variant.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Replace remaining automatic From conversions in parse_qcow and
BackingFile::new with explicit BlockError::new calls carrying the
appropriate BlockErrorKind at every error site.
Internal functions that still return qcow::Result (QcowHeader::new,
offset_is_cluster_boundary, clear_autoclear_features and others) are
wrapped with map_err at the boundary. These functions stay on
qcow::Result as they are internal to the qcow module and the
classification belongs at the call site rather than inside the
function itself.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch parse_qcow and BackingFile::new from qcow::Result to
BlockResult. Every early return site now produces an explicit
BlockError with the appropriate kind. Remaining internal calls to
functions still on qcow::Result rely on the From scaffolding and
will be converted in subsequent commits.
Two helpers are added to BlockError. with_kind replaces the
classification on an existing error, used in QcowDiskSync::new to
avoid double wrapping when the caller needs a different kind.
into_source consumes the error and returns the boxed source, used
at the recursive BackingFile open to extract the qcow::Error for
BackingFileOpen without letting qcow::Error hold a BlockError.
The qcow_sync boundary is simplified to a single closure that
operates on the BlockError already returned by parse_qcow.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch the public QcowFile constructors (new, new_from_backing,
new_from_header, from, from_backing, from_file_and_header) from
qcow::Result to BlockResult. Internal calls to header functions
that still return qcow::Result are wrapped with explicit error
classification at each call site.
Test assertions are updated to match on BlockErrorKind and use
downcast to inspect the underlying qcow::Error variant.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch l2_table(), refcount_block(), and first_zero_refcount()
to BlockResult. These are public inspection helpers with no
callers within the crate.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch the public convert() entry point to BlockResult. Inner
calls to functions already returning BlockResult propagate
naturally; those still returning qcow::Error get map_err
bridges.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch convert_reader() to BlockResult, preserving the original
qcow::Error variants as the BlockError source. The inner
convert_reader_writer() call now propagates naturally. Callers
get map_err bridges where they still return qcow::Error.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch convert_reader_writer() to BlockResult, preserving the
original qcow::Error variants as the BlockError source. The
inner convert_copy() call now propagates BlockResult naturally.
Callers get map_err bridges where they still return qcow::Error.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch convert_copy() to BlockResult, preserving the original
qcow::Error variants as the BlockError source for diagnostics.
A map_err bridge at the caller converts back where needed.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Switch the header dirty and corrupt bit helpers from
qcow::Result to BlockResult. Their callers either discard
the result or unwrap in tests, so no caller signatures change.
A map_err bridge in parse_qcow() converts back where needed.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Extend the BackingFileIo and BackingFileOpen variants of
qcow::Error with a path field so that backing file failures
report which file was involved. The path is populated from
the backing file configuration.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Implement AsFd for both RawFile and QcowRawFile by delegating to
the inner File handle. This enables safe fd borrowing through the
standard AsFd trait, which is a prerequisite for replacing unsafe
libc::dup calls with BorrowedFd::try_clone_to_owned().
Suggested-by: Rob Bradford <rbradford@rivosinc.com>
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Replace the clone based BackingFileOps trait with a BackingKind enum
so backing files can be decomposed into their concrete owned types.
BackingFile::new() for QCOW2 backings now calls parse_qcow() directly
instead of building a full QcowFile. Remove Clone for BackingFile and
QcowFile.
Prerequisite for the qcow_sync rewrite which decomposes a BackingFile
into a raw fd or QcowMetadata for lock free I/O.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Add resize() and grow_l1_table() so the metadata layer can grow
the virtual disk size. Only grow is supported.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Introduce QcowMetadata, a thread safe wrapper around QCOW2 metadata
tables and caches using RwLock.
Provides cluster resolution for reads and writes, and deallocate
operations for discard.
Extract parse_qcow() from QcowFile so both QcowFile and QcowDiskSync
can share the parsing and validation logic.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move L1 and L2 table entry helpers, division utilities and related
constants from mod.rs into a dedicated util.rs submodule. Both
mod.rs and metadata.rs import from util.
No functional changes.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Move QcowHeader, associated types, constants and helper functions
into a new header.rs submodule. Public types are re-exported from
mod.rs.
No functional changes.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
When a QCOW2 image has a backing file but backing_files=on is not set,
the error was MaxNestingDepthExceeded which gives no indication that
this is a policy decision or how to resolve it.
Add a BackingFilesDisabled error variant whose message indicates that
backing file support is disabled and references the backing_files
option. The translation from MaxNestingDepthExceeded to
BackingFilesDisabled happens at the QcowDiskSync boundary where the
policy decision is made, preserving the original error for genuine
recursive depth exhaustion.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Add comprehensive tests for DISCARD and WRITE_ZEROES operations:
QCOW2 zero flag test validates the complete workflow: allocate
cluster, DISCARD it, verify reads return zeros, write new data,
verify cluster reallocated.
QcowSync tests verify punch_hole and write_zeroes with Arc<Mutex<>>
sharing, including tests for cache consistency with multiple async
I/O operations.
RawFileSync tests verify punch_hole and write_zeroes using
fallocate.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Implement DISCARD using QCOW2 zero flag (bit 0 of L2 entries) with
sparse aware behavior.
When sparse=true - fully deallocate clusters by decrementing
refcount, clearing L2 entry, and reclaiming storage via punch_hole
when refcount reaches zero.
When sparse=false - use zero flag to keep storage allocated while
marking as reading zeros. Only works when cluster is not shared.
Shared clusters are fully deallocated.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Add sparse parameter to QcowFile constructors and propagate it from
device_manager through QcowDiskSync. This makes the sparse configuration
available throughout the QCOW2 implementation for controlling allocation
and deallocation behavior.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Add support for live resizing QCOW2 images. This enables growing
the virtual size of a QCOW2 disk while the VM is running.
Key features:
- Growing the image automatically expands the L1 table if needed
- Shrinking is not supported
- Resizing for images with backing files is not supported
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Test reading from overlay at offsets beyond backing file returns
zeros. Covers reads within backing range, beyond backing, and
boundary spanning.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
When an overlay QCOW2 image is larger than its backing file, reads
from offsets beyond the backing file virtual size would previously
fail with an I/O error.
The backing file virtual size is determined at open time and stored
for bounds checking during read operations:
- If the entire read is beyond the backing size, return all zeros
- If the read spans the boundary, read available data from backing and
fill the remainder with zeros
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Replace generic WritingHeader error with specific SyncingHeader
error for header fsync operations. This provides more precise
error reporting when syncing QCOW2 header changes to disk fails.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
QCOW2 v3 autoclear_features field contains bits for features whose
metadata becomes invalid when the image is modified by software that
doesn't understand them. Defined bits:
- Bit 0: Bitmaps extension
- Bit 1: Raw external data
Cloud-hypervisor doesn't support bitmaps or external data files, so
all autoclear bits are cleared on writable open. This signals other
tools that these features' data may be stale.
Readonly opens preserve autoclear bits unchanged.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Add tests for corrupt bit behavior during I/O operations.
- Unaligned L2 table address triggers corrupt bit on read
- Unaligned cluster address triggers corrupt bit on read and write
- Normal operations do not set the corrupt bit
- V2 images work correctly without feature bits
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Validate that L2 table offsets and refcount block offsets are cluster
aligned. Set the corrupt bit when unaligned offsets are detected, as
this indicates corrupted L1 or refcount table entries.
Validate that data cluster offsets from L2 entries are cluster aligned
during both reads and writes to existing clusters. Set the corrupt bit
when unaligned data cluster offsets are detected.
Prevent allocation of clusters at offset 0, which contains the QCOW2
header and should never be allocated. This catches corruption in the
available clusters list. Set the corrupt bit when this condition is
detected.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Set the QCOW2 corrupt bit when internal inconsistencies are detected
that indicate image metadata may be corrupted:
- Decompression decode failure, meaning compressed cluster data is
invalid
- Decompression size mismatch, where decompressed data doesn't match
expected cluster size
- Partial write after decompression, where L2 table was updated but
data cluster not fully written, leaving metadata inconsistent
- Invalid refcount index, where cluster address is outside valid
refcount table range, indicating a corrupted L2 entry
- Dirty L2 with zero L1 address, where L2 table is marked dirty but
L1 has no address for it
Note: Marking decompression failures as corrupt is more conservative
than QEMU, which returns EIO without setting the corrupt bit. This is
debatable since corrupted compressed data doesn't necessarily indicate
metadata corruption, but it provides a stronger safety guarantee by
preventing further writes to potentially damaged images.
Once set, the image can only be opened read-only until repaired with
qemu-img check -r.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Add comprehensive tests for the corrupt bit handling. Cover writable
rejection, read-only access, persistence, and dirty bit
coexistence.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Implement proper handling of the QCOW2 corrupt bit (incompatible feature
bit 1) according to the specification:
- Add Error::CorruptImage for rejecting writable opens of corrupt images
- Add CORRUPT to SUPPORTED features (handled specially, not rejected)
- Add QcowHeader::set_corrupt_bit() to mark images as corrupt
- Add QcowHeader::is_corrupt() helper method
- Reject writable opens of corrupt images with Error::CorruptImage
- Allow readonly opens of corrupt images with a warning
The corrupt bit indicates that image metadata may be inconsistent. Per
spec, such images must not be written to until repaired by external
tools like qemu-img. Read-only access is permitted to allow data
recovery.
Users can open corrupt images read-only using:
--disk path=/path/to/image.qcow2,readonly=on
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Update QcowHeader and other related places to use BeUint methods
internally for reading/writing header fields.
This removes the byteorder dependency from mod.rs and consolidates
all big-endian file I/O through the shared BeUint trait.
Suggested-by: Rob Bradford <rbradford@rivosinc.com>
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Add a read_be() method to the BeUint trait and make it pub(super)
so it can be used across the qcow module. Change BeUint::write_be()
to take Self instead of u64, providing type safety through TryFrom
conversion.
Suggested-by: Rob Bradford <rbradford@rivosinc.com>
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Verify dirty bit is set on open and cleared on close for v3 images.
Ensure v2 and read-only files are not affected. Update existing
tests.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>