Commit Graph

536 Commits

Author SHA1 Message Date
Anatol Belski
3f20fd0759 block: qcow: Drop own O_DIRECT alignment handling
The qcow workers carried their own O_DIRECT alignment requirement
and bounced unaligned cluster accesses through AlignedBuffer. Now
that the data file is an AlignedFile that handles O_DIRECT
transparently, the qcow layer can read and write through plain
buffers and let AlignedFile perform the aligned bounce.

Remove the alignment field and the per cluster AlignedBuffer RMW
branches from both the sync and async workers. The async io_uring
fast path still needs to avoid submitting unaligned guest iovecs
under O_DIRECT, so gate it on is_direct rather than on a stored
alignment value.

Drop the QcowAsync alignment override so it reports the trait
default sector size, matching QcowSync. qcow never submits guest
iovecs to the kernel under O_DIRECT, so reporting a larger value
only forced the request layer into an extra bounce buffer.

This adds one buffer copy per unaligned O_DIRECT cluster but moves
all alignment handling into a single place. The buffered path is
unchanged.

With qcow no longer the only caller, AlignedBuffer::read_exact_from
becomes dead code, so remove it and switch its tests to read_from.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-20 11:46:17 +00:00
Anatol Belski
6633072a28 block: vhd: Enable O_DIRECT for fixed VHD images
Thread the direct flag from the disk open options through VhdDisk into
the AlignedFile the workers run on, so a fixed VHD opened with direct=on
issues O_DIRECT I/O instead of buffered I/O. Alignment is probed once on
that AlignedFile and reused by the sync and io_uring workers.

Advertise host topology from VhdDisk::topology by probing the underlying
file. On a 4096 byte sector filesystem opened with O_DIRECT this reports
logical_block_size 4096 to the guest, so the guest never issues 512 byte
I/O that the host kernel would reject as misaligned. Falls back to the
default topology with a warning when the probe fails.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-20 11:46:17 +00:00
Anatol Belski
693987b9e2 block: vhd: Read footer via AlignedFile
Read the trailing footer sector through an AlignedFile rather than
probing the device topology and reading a full logical block. The
AlignedFile bounce buffer serves the trailing sector of an O_DIRECT
fd whose offset is unaligned against the device block size, so the
read no longer fails with EINVAL on a 4k sector backing store.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-20 11:46:17 +00:00
Anatol Belski
2359003001 block: raw: Handle O_DIRECT in the raw async workers
The raw sync, io_uring and AIO workers now own an AlignedFile and use
it for the O_DIRECT alignment value and for the unaligned fallback.
Aligned operations keep the fast preadv and pwritev iovec path straight
to the kernel. When the offset or an iovec base or length is not a
multiple of the probed alignment, the worker gathers the iovecs into
one contiguous host buffer and runs a synchronous RMW through
AlignedFile, then scatters the result back into guest memory.

RawDisk constructs the AlignedFile from the disk file and the direct
flag and passes it into each worker, so alignment is probed once at
open time. The fixed VHD workers are threaded through the same
AlignedFile based constructors using a non-direct AlignedFile to
preserve current behavior.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-20 11:46:17 +00:00
Anatol Belski
516f4e447d block: aligned: Add seeking cursor and drop RawFile
RawFile wrapped AlignedFile only to add a seek position and the file
trait impls that the qcow and vhost_user_block code expects. Fold that
position and every impl onto AlignedFile so the wrapper layer goes away
and callers work with a single O_DIRECT aware file type.

AlignedFile now tracks a cursor and implements Read, Write, Seek,
WriteZeroesAt, PunchHole, FileSync, SeekHole, BlockBackend, Clone,
AsRawFd and AsFd in addition to the positional FileExt path. The
direct_io flag is dropped because alignment already encodes it, where
a zero alignment means the file was not opened with O_DIRECT.

All RawFile uses in the qcow internals and vhost_user_block move to
AlignedFile, and raw_file.rs is removed.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-20 11:46:17 +00:00
Anatol Belski
4c7e2b83c1 block: aligned: Query direct alignment in AlignedFile
Move the statx STATX_DIOALIGN probe out of DiskTopology into a free
probe_direct_alignment helper keyed on a raw fd. The helper gates on the
O_DIRECT open flag and returns the kernel reported alignment only when
direct I/O is in effect, and None otherwise. DiskTopology::probe keeps
the same call path and result.

AlignedFile::new now determines its O_DIRECT block alignment from
probe_direct_alignment instead of trial reads at 512 and 4096, falling
back to SECTOR_SIZE when the kernel does not report a value. This
matches how the raw and fixed VHD workers determine alignment, so all
backends agree on one source of truth.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-20 11:46:17 +00:00
Rob Bradford
7e2e7a164b block: Drain io_uring in-flight operations on teardown
Closing an io_uring fd does not synchronously finish requests that
already reached the kernel. During block worker teardown this can let
an io-wq worker keep using retained guest-memory iovecs after reset.

Drain UringDataIo in Drop: retry any published SQEs and wait for CQEs
until no retained operation remains. If draining fails, leak retained
buffers. Drop QcowAsync's ring before its data fd so retrying
published SQEs still uses a valid descriptor.

To avoid a potential infinite loop when completions fail to be delivered
cap the number of iterations of the loop (2x the number of inflight
requests).

Assisted-by: Codex:GPT-5
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-18 17:35:27 +00:00
Henry Hrvoje Tonkovac
6683ae2d51 block: trim qualified paths in vhdx tests
Import the std modules used in the test module instead of spelling the
full paths at every use site, and drop the now-unnecessary
#[expect(clippy::absolute_paths)] on the vhdx internal test module.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-17 17:14:44 +00:00
Tushar Khatri
7be97937ef block: reevaluate #[allow] attributes
Convert the still-needed #[allow]s to #[expect] so they warn if the
lints stop firing.

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-17 17:14:00 +00:00
Alexander Lvov
e0801bda3b block: vhd: fix incomplete bounds check in sync I/O worker
The sync I/O worker only checked that the operation offset did
not start past the end of the virtual disk (offset >= size) -
did not verify that the operation end (offset + len) stays
within bounds.

A read or write that started inside the image but extended
beyond the logical size was silently passed to the raw backend.

The async io_uring worker already had the correct check
(offset + len > size with overflow protection). I extracted it
into a shared helper in worker/common.rs and reused inside the
sync path to eliminate duplication and close the gap.

Fixes #8311

Signed-off-by: Alexander Lvov <alexander.lvov.git@gmail.com>
2026-06-17 14:26:25 +00:00
Rob Bradford
2bc968ba1d build: Deny clippy::absolute_paths
Removal of absolute paths is currently in progress. To avoid regressing
those changes add a clippy deny at the workspace level and at the crate
level override with #[expect(clippy::absolute_paths)]

See: #7670

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-17 14:38:25 +01:00
Pascal Scholz
257a00547a block: Retry locking when interrupted by EINTR
Acquiring an image lock can be interrupted with EINTR. In this case, we
returned with an error. Instead, we now retry acquiring the lock.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
2026-06-17 08:44:50 +00:00
Rob Bradford
b4c1d85327 block: vhdx: Use AlignedFile for O_DIRECT-safe I/O
By redirecting VHDx I/O through the AlignedFile the required RMW
semantics can be achieved for writes less than the logical block size
whilt reusing the same logic used for other backend implementations.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-16 14:43:11 +00:00
Rob Bradford
80a0393edd block: qcow: Port RawFile to AlignedFile
Reuse the functionality in the AlignedFile wrapper for the QCOW RawFile
wrapper. This makes alignment handling more transparent.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-16 14:43:11 +00:00
Rob Bradford
3fe5225a44 block: Add AlignedFile
Provide a single home for O_DIRECT alignment and RMW behavior behind an
std::os::unix::fs::FileExt implementation built on AlignedBuffer.

Unaligned requests are bounced through an AlignedBuffer (applying RMW
for writes) and aligned requests pass straight through to the inner
File.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-16 14:43:11 +00:00
Rob Bradford
201ddaef55 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>
2026-06-16 14:43:11 +00:00
Rob Bradford
108d251c1d block: qcow: Port to AlignedBuffer
Replace the qcow specific AlignedBuf along with the pread64/pwrite64
helpers with the new common AlignedBuffer implementation.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-16 14:43:11 +00:00
Rob Bradford
2e2167368e 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>
2026-06-16 14:43:11 +00:00
Rob Bradford
30e79de26f block: qcow: Port RawFile to AlignedBuffer
Replace the manual alloc_zeroed/dealloc and pread64/pwrite64 with use of
the new AlignedBuffer structure.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-16 14:43:11 +00:00
Rob Bradford
4ad95e1cc6 block: Add AlignedBuffer
The block code repeatedly open-codes O_DIRECT alignment calculations and
bounce-buffer allocation at each I/O site. Add an AlignedBuffer struct
that handles the alignment and allocation in one place, using FileExt
(read_exact_at and write_all_at) for the I/O (so no need for custom libc
wrappers).

The caller creates an AlignedBuffer with an offset, length and
alignment, then uses read_from and write_to for aligned I/O and as_slice
and as_mut_slice to access the logical data portion within the aligned
region.

This is a lot like the AlignedBuf that was already existing in the QCOW2
code but is a more generalised version.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-16 14:43:11 +00:00
Rob Bradford
a8b059328f block: Implement FileExt for RawFile
Implement std::os::unix::fs::FileExt for RawFile by delegating to the
inner File. This enables callers holding a reference to a RawFile to use
read_exact_at and write_all_at directly for non-cursor I/O (like pread,
etc) without going through custom helper functions.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-16 14:43:11 +00:00
Henry Hrvoje Tonkovac
e5f32e986f block: trim qualified paths in io and lib
Import the std modules used in the crate instead of spelling the full
paths at every use site.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-16 10:52:57 +00:00
Anatol Belski
0e4e3277a9 block: Add WriteZeroes sector overflow regression test
Cover the prior commit by constructing a Request directly and a stub
AsyncIo whose backend methods are unreachable, then submit a payload
with sector + num_sectors past u64::MAX and assert BadRequest.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-15 13:54:39 +01:00
Anatol Belski
f7ebb4b871 block: Bounds check WriteZeroes before sector multiplication
In Request::execute_async the WriteZeroes arm multiplied wz_sector
by SECTOR_SIZE before the checked_add of sector and num_sectors.
A wz_sector near u64::MAX overflows the multiplication.

Reorder the arm to run the checked_add and disk_nsectors check
first, matching the Discard arm above.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-15 13:54:39 +01:00
Henry Hrvoje Tonkovac
2c22159802 block: trim qualified paths in formats
Import the std modules used in the disk-format handlers instead of
spelling the full paths at every use site.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-15 08:22:19 +00:00
Rob Bradford
829676e640 block: qcow: Delete QcowFile
Delete the now unused QcowFile implementation it was only used for
creating disk images for the tests and for the performance-metrics. It
was not used for the virtio-block device.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-08 13:19:32 +00:00
Rob Bradford
1120fe74f5 block: qcow: Extract rebuild_refcounts() function
Pull rebuild_refcounts out of QcowFile so QcowFile can be removed in a
follow up commit. This function is still required by parse_qcow().

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-08 13:19:32 +00:00
Rob Bradford
9da113ff1f block: qcow: Port internal tests to QcowDisk
Port the internal/mod.rs tests from QcowFile to QcowDisk so
the surviving tests exercise the code paths that are hit when used via
QcowDisk (and thus virtio-block).

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-08 13:19:32 +00:00
Rob Bradford
34e8e3dbf9 performance-metrics: Switch to QcowDisk
Switch from QcowFile to QcowDisk taking advantage of QcowTempDisk where
appropriate.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-08 13:19:32 +00:00
Rob Bradford
cfce14edd1 block: qcow: Port tests to QcowTempDisk
Port tests over to QcowTempDisk and also over to QcowDisk rather than
QcowFile where necessary.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-08 13:19:32 +00:00
Rob Bradford
31ee5e99e4 block: qcow: Add QcowTempDisk helper
A common pattern in the test code is to create a temporary file, format
it as QCOW2 and then open it as a QcowDisk. Create a helper struct that
can be used in those tests. This is marked as #[cfg(test)] as initially
it will only be used by the test suite.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-08 13:19:32 +00:00
Rob Bradford
b3cf8a84cd block: qcow: Add qcow::create_image()
Add a method to format a file as a QCOW2 file which will mainly be used
by the test infrastructure. This copies the logic from QcowFile. It
doesn't refactor it as the removal of QcowFile is planned.

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-08 13:19:32 +00:00
Philipp Schuster
e9fa6e6295 block: remove unneeded #[allow(dead_code)]
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-03 13:56:31 +00:00
Philipp Schuster
1c484e8725 block: streamline inclusion of test-only code
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-03 13:56:31 +00:00
Anatol Belski
362a9ecc4f block: qcow: Test rejection of backing file offset with zero size
Cover the malformed header case where backing_file_offset is non
zero but backing_file_size is zero, which must be rejected with the
new dedicated error.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
679892f56e block: qcow: Test rejection of backing file size with zero offset
Cover the malformed header case where backing_file_offset is zero
but backing_file_size is non zero, which must be rejected with the
new dedicated error.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
05cac5657c block: qcow: Test rejection when backing file overlaps header
Cover the case where backing_file_offset points inside the fixed
header fields, which the new header overlap check must reject.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
9c85aab85d block: qcow: Test backing file fitting exactly at cluster end
Cover the boundary positive case where backing_file_offset plus
backing_file_size equals the cluster size, which the spec allows
and the new bound check must accept.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
cdd7220384 block: qcow: Test rejection when backing file end exceeds cluster
Cover the case where backing_file_offset lies inside the first
cluster but backing_file_offset + backing_file_size crosses the
cluster boundary, so the end of the name spills outside.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
a4e8d79650 block: qcow: Test rejection when backing file offset exceeds cluster
Cover an offset that lies well past the end of the first cluster
to make sure the bound check fires for arbitrary out of range
offsets rather than only the boundary case.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
2d8811ad82 block: qcow: Test rejection when backing file offset equals cluster size
A backing file string placed exactly at cluster_size starts past
the first cluster boundary, so QcowHeader::new must reject it.

Introduce a read_header_with_patched_backing helper that builds
a valid header, patches backing_file_offset and backing_file_size,
writes it out and re-parses it. Use it to cover this case.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
53a9ae08c2 block: qcow: Reject backing file offset with zero size
A qcow2 header with non-zero backing_file_offset that points at a
zero length name is malformed. The parser would otherwise read an
empty path string and store it as a backing file. Reject it with a
dedicated error so the user gets a clear diagnostic.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
39e9376f5b block: qcow: Reject backing file size with zero offset
A qcow2 header with backing_file_offset == 0 indicates the image
has no backing file, so any non-zero backing_file_size is malformed.
Qemu silently ignores the size in this case, which hides image
corruption. Reject it explicitly with a dedicated error so the user
gets a clear diagnostic.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
4294a4b862 block: qcow: Reject backing file name overlapping the header
The qcow2 spec requires the backing file name string to live in the
remaining space between the end of the header extension area and
the end of the first cluster. Header parsing accepted any backing
file offset above zero, including offsets pointing into the fixed
header fields themselves, so a corrupt or malicious image could
redirect the parser into reinterpreting header bytes as the backing
path.

Reject any backing_file_offset that is less than header.header_size
via a new BackingFileOverlapsHeader error. The check fires before
the existing first cluster bound, since an overlap is a different
class of corruption and deserves a distinct diagnostic.

Ref: #8261
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Anatol Belski
4a2a9390be block: qcow: Reject backing file name outside first cluster
The qcow2 spec requires the backing file name string to live
entirely within the first cluster, between the end of the header
extension area and the cluster boundary. The parser previously
only validated the 1023 byte cap on the name length and accepted
any backing_file_offset, so a corrupt or malicious image could
place the name string anywhere in the file.

Add the cluster bound check in QcowHeader::new and report it via
a new BackingFileOutsideFirstCluster error.

Fixes: #8261

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-01 16:27:50 +00:00
Ian Klemm
a667d85055 block: qcow: reject out-of-bounds cluster offsets
Standard L2 data offsets and L1-referenced L2 table offsets must be
aligned and covered by the current refcount table. The write path
checked both constraints at one call site, while read, cache population,
and deallocation paths only checked alignment or relied on later
refcount lookup errors.

Centralize the validation in QcowState and use it before reading L2
tables, mapping standard L2 entries for reads and writes, and
deallocating existing clusters. Invalid offsets set the corrupt bit and
fail with EIO before data I/O or refcount updates.

Add QcowSync regression tests that corrupt a standard L2 entry past the
refcount-addressable range and verify that reads and writes fail with
EIO and mark the header corrupt.

Assisted-by: Codex:GPT-5
Signed-off-by: Ian Klemm <hi@ianklemm.de>
2026-05-29 15:22:57 +00:00
Ian Klemm
17b6fd91ca block: qcow: decouple pointer table writes from cursor state
write_pointer_table() used a BufWriter over a cloned fd because the
per-entry callback also needs mutable access to QcowRawFile.

That couples the final write location to the ambient kernel cursor while
the callback is allowed to perform metadata I/O. Materialize the encoded
entries first, then seek and write the table after callback execution
has finished.

This keeps the pointer-table write independent from current and future
callback behavior without depending on proving that a cursor-moving
callback is reachable in today's synchronous CH path.

Apply the same materialize-then-write shape to
write_pointer_table_direct() for consistent semantics, and cover both
paths with unit tests.

Assisted-by: Codex:GPT-5
Signed-off-by: Ian Klemm <hi@ianklemm.de>
2026-05-29 15:22:57 +00:00
Anatol Belski
4192f5101b block: Validate descriptor buffer ranges via checked iterator
Switch Request::parse over to the CheckedDescriptorIter helper from
vm-virtio so the block crate validates each descriptor's translated
(addr, len) range against guest memory through the same centralized
path used by virtio-devices. Any descriptor whose buffer is not fully
backed by guest RAM is now rejected before any I/O is set up against
it.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
c80aaa1b58 block: Remove vhdx compat alias
Re-export Vhdx from formats::vhdx and update the fuzz target to
use block::formats::vhdx::Vhdx. Remove the vhdx compat alias
from lib.rs.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
2c8978bbf2 block: Remove qcow compat alias
Update external consumers to use formats::qcow::internal instead
of the top level qcow alias. Keep a crate private use for the
QcowError variant in lib.rs.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00