Commit Graph

591 Commits

Author SHA1 Message Date
Alyssa Ross
cd2089eb69 build: Bump vm-memory and dependents
vm-memory 0.18 has renamed GuestMemory to GuestMemoryBackend, and made
GuestMemory refer to something less specific.  For simplicity, we keep
using GuestMemoryBackend (formerly GuestMemory) everywhere for now.  We
can adjust bounds to be less specific later if we find ourselves needing
the newly enabled flexibility.

Signed-off-by: Alyssa Ross <hi@alyssa.is>
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-20 17:51:23 +00:00
CMGS
b68e7f3d91 block: flush cached qcow2 metadata on device pause
The qcow2 backend caches L2 table and refcount updates in memory and
only writes them back on a guest-initiated flush, clean shutdown or
drop. A paused VM therefore leaves the on-disk image without the
mappings for any cluster allocated since the last guest flush: the
data clusters are present in the file, but nothing references them.

Anything that reads the image while the VM is paused sees those
writes as missing. Copying the disk alongside vm.snapshot (the
documented snapshot workflow) captures a stale image, and live
migration releases the disk locks after pausing so the destination
reopens the file with the same stale metadata. In both cases writes
the guest has completed, and may later read back, silently disappear.

Add a MetadataSync capability trait with a no-op default, fold it
into FullDiskFile, implement it for the qcow2 backend as a metadata
cache flush, and call it from the virtio-block pause path after
in-flight requests have drained. Pause is the quiesce point both
flows rely on, and it is a cold path, so the extra flush does not
affect runtime I/O.

Reproduced by writing to a qcow2 disk from the guest with O_DIRECT
and no explicit flush, pausing the VM and copying the image: qemu-img
map on the copy shows no mapped clusters and reads return zeros. With
this change the copy contains every completed write. A unit test
covers the same sequence at the format level: a completed write is
invisible to a fresh reader until sync_metadata, and visible after.

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-07-19 18:26:37 +00:00
Anatol Belski
9474c07526 block: sparse: Add unit tests for write_zeroes and punch_hole fallback
Exercise the EOPNOTSUPP fallback path using memfd, which shares
the same backing as tmpfs and triggers the fallback without
requiring privileges or a specific filesystem.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-18 17:47:50 +00:00
Anatol Belski
028c99b39c block: raw: Remove EOPNOTSUPP skip from test_write_zeroes
The WriteZeroesAt fallback means write_zeroes no longer fails on
filesystems that lack fallocate support. The skip is now dead
code.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-18 17:47:50 +00:00
Anatol Belski
d7a7d73622 block: Fall back to write when fallocate returns EOPNOTSUPP
Filesystems such as tmpfs do not support fallocate with
FALLOC_FL_ZERO_RANGE or FALLOC_FL_PUNCH_HOLE and return EOPNOTSUPP.
When a raw disk image lives on such a filesystem, virtio write zeroes
and discard requests fail with IOERR.

Use the WriteZeroesAt trait from vmm_sys_util through AlignedFile,
which already bundles fallocate with a positional write fallback.
For punch_hole, catch EOPNOTSUPP and fall back to the same trait.

The io_uring engine previously submitted fallocate directly through
the ring, where the async EOPNOTSUPP completion had no retry path.
Route it through the same sync helpers that the other engines
already use.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-18 17:47:50 +00:00
Anatol Belski
2ea2209920 block: qcow: Test reopen finds no stranded clusters
After churning a single L2 region with an fsync per write, close and
reopen the image. parse_qcow rebuilds the free list from the on disk
refcounts, so a reopen that recovers many clusters means the runtime
allocator stranded them. Without the fix the reopen recovered every
relocated cluster. Now it finds none.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-16 13:03:48 +00:00
Anatol Belski
8acefad893 block: qcow: Test freed clusters stay on the free list
After churning a single L2 region with an fsync per write, scan the file
for refcount==0 clusters and assert every one is on the runtime free
list. The reuse bug left relocated refcount block clusters free on disk
but off the list, so this fails without the fix, reporting the exact
count of stranded clusters.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-16 13:03:48 +00:00
Anatol Belski
365d6b8017 block: qcow: Test relocated metadata cluster reuse
Add a regression test that churns one L2 region with an fsync per write,
relocating the L2 table and its refcount block every iteration, and
asserts committed host blocks track live data instead of doubling. Data
written across the churn is read back to confirm integrity.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-16 13:03:48 +00:00
Anatol Belski
57f67a543a block: qcow: Reuse relocated refcount block clusters
Metadata tables use relocate on write. The first modification after a
flush writes the table to a new cluster and frees the old one. The L2
path returns the old cluster to the free list, but the refcount block
path did not. QcowState::set_cluster_refcount set the dropped refcount
block cluster to refcount 0 yet never pushed it onto unref_clusters, so
it stayed committed on the host and was invisible to the allocator.
Every refcount block relocation stranded one cluster, and
get_new_cluster extended the file instead of reusing it.

Under a sync heavy workload each fsync that dirties a table relocates
it, so the physical footprint grew at roughly twice the live data
without bound. qemu-img check stayed clean because the refcounts are
correct, and a reopen, which rebuilds the free list from refcounts,
recovered the space.

Push the dropped refcount block cluster onto unref_clusters so the
running allocator reuses it, matching the L2 table path.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-16 13:03:48 +00:00
dependabot[bot]
8588981863 build(deps): bump the non-rust-vmm group across 2 directories with 13 updates
Bumps the non-rust-vmm group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.52` | `0.8.53` |
| [crc-any](https://github.com/magiclen/crc-any) | `2.5.1` | `3.0.0` |
| [rand](https://github.com/rust-random/rand) | `0.10.1` | `0.10.2` |
| [ssh2](https://github.com/alexcrichton/ssh2-rs) | `0.9.5` | `0.9.6` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.65` | `1.2.66` |
| [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) | `0.8.21` | `0.8.22` |
| [defmt](https://github.com/knurling-rs/defmt) | `1.1.0` | `1.1.1` |
| [jobserver](https://github.com/rust-lang/jobserver-rs) | `0.1.34` | `0.1.35` |
| libredox | `0.1.17` | `0.1.18` |
| [rustls-pki-types](https://github.com/rustls/pki-types) | `1.14.1` | `1.15.0` |

Bumps the non-rust-vmm group with 5 updates in the /fuzz directory:

| Package | From | To |
| --- | --- | --- |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.52` | `0.8.53` |
| [rand](https://github.com/rust-random/rand) | `0.10.1` | `0.10.2` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.65` | `1.2.66` |
| [jobserver](https://github.com/rust-lang/jobserver-rs) | `0.1.34` | `0.1.35` |
| [rustls-pki-types](https://github.com/rustls/pki-types) | `1.14.1` | `1.15.0` |



Updates `zerocopy` from 0.8.52 to 0.8.53
- [Release notes](https://github.com/google/zerocopy/releases)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.52...v0.8.53)

Updates `crc-any` from 2.5.1 to 3.0.0
- [Commits](https://github.com/magiclen/crc-any/compare/v2.5.1...v3.0.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `ssh2` from 0.9.5 to 0.9.6
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/0.9.5...0.9.6)

Updates `cc` from 1.2.65 to 1.2.66
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.65...cc-v1.2.66)

Updates `crossbeam-utils` from 0.8.21 to 0.8.22
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/main/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.21...crossbeam-utils-0.8.22)

Updates `defmt` from 1.1.0 to 1.1.1
- [Release notes](https://github.com/knurling-rs/defmt/releases)
- [Changelog](https://github.com/knurling-rs/defmt/blob/main/CHANGELOG.md)
- [Commits](https://github.com/knurling-rs/defmt/compare/defmt-v1.1.0...defmt-v1.1.1)

Updates `defmt-macros` from 1.1.0 to 1.1.1
- [Release notes](https://github.com/knurling-rs/defmt/releases)
- [Changelog](https://github.com/knurling-rs/defmt/blob/main/CHANGELOG.md)
- [Commits](https://github.com/knurling-rs/defmt/compare/defmt-macros-v1.1.0...defmt-macros-v1.1.1)

Updates `jobserver` from 0.1.34 to 0.1.35
- [Commits](https://github.com/rust-lang/jobserver-rs/compare/0.1.34...0.1.35)

Updates `libredox` from 0.1.17 to 0.1.18

Updates `libssh2-sys` from 0.3.1 to 0.3.2
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/libssh2-sys-0.3.1...libssh2-sys-0.3.2)

Updates `rustls-pki-types` from 1.14.1 to 1.15.0
- [Release notes](https://github.com/rustls/pki-types/releases)
- [Commits](https://github.com/rustls/pki-types/compare/v/1.14.1...v/1.15.0)

Updates `zerocopy-derive` from 0.8.52 to 0.8.53
- [Release notes](https://github.com/google/zerocopy/releases)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.52...v0.8.53)

Updates `zerocopy` from 0.8.52 to 0.8.53
- [Release notes](https://github.com/google/zerocopy/releases)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.52...v0.8.53)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `cc` from 1.2.65 to 1.2.66
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.65...cc-v1.2.66)

Updates `jobserver` from 0.1.34 to 0.1.35
- [Commits](https://github.com/rust-lang/jobserver-rs/compare/0.1.34...0.1.35)

Updates `rustls-pki-types` from 1.14.1 to 1.15.0
- [Release notes](https://github.com/rustls/pki-types/releases)
- [Commits](https://github.com/rustls/pki-types/compare/v/1.14.1...v/1.15.0)

Updates `zerocopy-derive` from 0.8.52 to 0.8.54
- [Release notes](https://github.com/google/zerocopy/releases)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.52...v0.8.53)

---
updated-dependencies:
- dependency-name: zerocopy
  dependency-version: 0.8.53
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: crc-any
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: rand
  dependency-version: 0.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: ssh2
  dependency-version: 0.9.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.66
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: crossbeam-utils
  dependency-version: 0.8.22
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: defmt
  dependency-version: 1.1.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: defmt-macros
  dependency-version: 1.1.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jobserver
  dependency-version: 0.1.35
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libredox
  dependency-version: 0.1.18
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libssh2-sys
  dependency-version: 0.3.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rustls-pki-types
  dependency-version: 1.15.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.53
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.53
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand
  dependency-version: 0.10.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.66
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jobserver
  dependency-version: 0.1.35
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rustls-pki-types
  dependency-version: 1.15.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.54
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 00:53:35 +00:00
Rob Bradford
1ba5f15198 block: Check request total length is a multiple of SECTOR_SIZE
The request can be spread over multiple descriptors but the virtio-block
specification (and this code) expects that is a whole number of sectors
(512 bytes).

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-08 17:11:59 +00:00
Rob Bradford
5b99f9ce41 block: qcow: Preserve the compression type when writing the header
When writing the header after a resize the compression type field was
always set to 0, which selects zlib, even when the image was originally
created with zstd. The resized image would then no longer be usable.

Write the actual configured compression type instead.

Fixes: #8558

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-08 15:40:26 +00:00
Rob Bradford
8d3859af0d block: qcow: Read/write on disk structure via zerocopy on a struct
Use a pair of structs for the basic QCOW V2 header and V3 additional
fields and serialize them to/from disk via the zerocopy crate.

This removes the need to manage the position to read from/write to.

Assisted-by: Claude:Opus-4.8

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-08 15:40:26 +00:00
Anatol Belski
039b4e6013 block: vhdx: Flatten internal and worker modules
Remove the internal and worker submodule layers from the VHDX format
directory. The bat, header, io, and metadata parsers move up as
direct children, internal/mod.rs becomes parser.rs, and the sync
backend moves up as engine_sync.rs. The declaration only
worker/mod.rs is dropped.

The public types are surfaced at the vhdx module level, so callers
use block::formats::vhdx instead of reaching into the internal
module.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-08 12:33:30 +00:00
Anatol Belski
6e0c39964a block: vhd: Flatten internal and worker modules
Remove the internal and worker submodule layers from the VHD format
directory. The footer and fixed parsers move up as footer.rs and
fixed.rs, and the backends move up as engine_sync.rs and
engine_uring.rs. Both internal/mod.rs and worker/mod.rs held only
module declarations and are dropped.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-08 12:33:30 +00:00
Anatol Belski
ecf72ba787 block: raw: Flatten worker module
Remove the worker submodule layer from the raw format directory. The
backend files move up as engine_sync.rs, engine_uring.rs, and
engine_aio.rs, the shared test helpers move up as tests.rs, and the
two alignment helper functions from worker/mod.rs merge into the raw
module.

The vhd backends that reused the raw io_uring and sync engines are
updated to the new block::formats::raw::engine_* paths.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-08 12:33:30 +00:00
Anatol Belski
7120311462 block: qcow: Flatten internal and worker modules
Remove the internal and worker submodule layers from the QCOW2
format directory. The former internal files become direct children
of qcow, with internal/mod.rs turning into parser.rs. The worker
backends move up as engine_sync.rs and engine_uring.rs, and
worker/mod.rs, which held only module declarations, is dropped.

The public parser types are now surfaced at the qcow module level,
so external callers use block::formats::qcow instead of reaching
into the internal module.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-08 12:33:30 +00:00
Rob Bradford
ad3dbcd130 virtio-devices: Use SmallVec for descriptor chains
Rather than instantiating a vector for parsing the descriptor chain in
advance instead use a SmallVec bounded by the expected length of the
descriptor chain. This removes vector allocations from those paths.

As smallvec was already a block dependency move it to a workspace
dependency and use it from there.

Fixes: #5079

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-08 02:15:19 +00:00
Alexander Lvov
fa7cad4aee block: vhdx: enable bounds check in sync I/O worker
VhdxSync::submit_data_operation() passed every read/write straight to
the underlying Vhdx without checking the request against the virtual
disk's logical size. A request that started inside the image but
extended past its end (or an offset past the end entirely) was passed
through unchecked, silently reading/writing out of the intended
bounds.

Call AsyncIoOperation::validate_bounds() from submit_data_operation()
before dispatching the operation, the same way the VHD sync worker
does. The check rejects any request whose offset + length exceeds the
logical size.

Signed-off-by: Alexander Lvov <alexander.lvov.git@gmail.com>
2026-07-07 09:56:20 +00:00
Alexander Lvov
4bb3e1ca04 block: vhd: switch to AsyncIoOperation::validate_bounds
Reuse global validate_bounds() operation helper instead of
having a local implementation in vhd/worker/common.rs

Signed-off-by: Alexander Lvov <alexander.lvov.git@gmail.com>
2026-07-07 09:56:20 +00:00
Alexander Lvov
73efde72b3 block: vhdx: extract dynamic_vhdx test helper into test_util
Extract the dynamic VHDX qemu-img helper into a shared vhdx::
test_util module to reuse inside the upcoming VhdxSync bounds-check.

Signed-off-by: Alexander Lvov <alexander.lvov.git@gmail.com>
2026-07-07 09:56:20 +00:00
Alexander Lvov
43c29096eb block: async_io: add AsyncIoOperation::validate_bounds
Implement global helper to validate vhd and vhdx sync workers'
I/O requests whose offset + length exceeds the virtual disk's
logical size.

Signed-off-by: Alexander Lvov <alexander.lvov.git@gmail.com>
2026-07-07 09:56:20 +00:00
Rob Bradford
308033de40 block: Fix clippy: unused import
```
warning: unused import: `Bytes`
   --> block/src/io/request.rs:659:21
    |
659 |     use vm_memory::{Bytes as _, GuestMemoryMmap};
    |                     ^^^^^^^^^
    |
    = note: `-D unused-imports` implied by `-D warnings`
```

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-07 08:31:22 +00:00
Rob Bradford
f5468a6dae block: Fix clippy: chunks_exact with constant chunk size
```
warning: using `chunks_exact` with a constant chunk size
   --> block/src/formats/qcow/internal/header.rs:253:39
    |
253 |                     for entry in data.chunks_exact(FEATURE_NAME_ENTRY_SIZE) {
    |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `as_chunks` instead
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#chunks_exact_to_as_chunks
    = note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D clippy::all`
```

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-07 08:31:22 +00:00
Sayed Kaif
6882276e2d block: vhdx: reject overflowing region table entries
The region table overlap fix in RegionInfo::new computes each entry's
end offset as `file_offset + length`. Both values are taken verbatim
from the image, so a crafted or corrupt VHDX can set a file offset near
u64::MAX and make that addition wrap. A wrapped end offset compares as a
small value, which can slip a genuinely overlapping region past the
half-open interval check that #8483 added.

Use checked_add for the end offset and return a new RegionEntryOverflow
error when it wraps, so a malformed entry is rejected instead of being
folded into a valid-looking range. The computed end is now reused for
the region_entries map so the bound is only calculated once. Add a
regression test for a wrapping entry.

Signed-off-by: Sayed Kaif <metsw24@gmail.com>
2026-07-06 22:50:24 +00:00
Anatol Belski
67095b0da1 block: Drop the AlignedFile Read, Write, and Seek impls
Nothing reads or writes the AlignedFile through a cursor anymore, so
remove the Read, Write, and Seek impls together with the in memory
position field. SeekHole no longer tracks a position. The cursor unit
tests move to read_at and write_at, dropping the one that duplicated
existing coverage.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-06 14:41:08 +00:00
Anatol Belski
15b59fdd9f block: Execute synchronous block requests positionally
Request::execute seeked the disk to the request sector and then read or
wrote sequentially through the cursor. Walk a running offset with
read_exact_at and write_all_at instead, and flush with fsync, so the
bound becomes FileExt and FileSync rather than Seek, Read, and Write.

The seek step is gone, so drop the now unused ExecuteError::Seek.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-06 14:41:08 +00:00
Anatol Belski
06cad2cdcd block: Detect the image type positionally
detect_image_type read the first block through the AlignedFile Read
cursor. Read it with read_exact_at at offset 0 instead, so image type
detection no longer depends on the cursor.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-06 14:41:08 +00:00
Anatol Belski
26080f264a block: vhd: Read the VHD footer positionally
Read the trailing footer sector with query_device_size and
read_exact_at instead of seeking to the end of the AlignedFile and
reading through its cursor.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-06 14:41:08 +00:00
Anatol Belski
899c2df248 block: qcow: Make the qcow image compressor test positional
Convert the qcow image compressor test helper to positional access
instead of the cursor. Test only change.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-03 20:29:52 +00:00
Anatol Belski
6be2a52d14 block: qcow: Make the qcow sync worker tests positional
Convert the qcow sync worker test helpers to positional access instead
of the cursor. Test only change.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-03 20:29:52 +00:00
Anatol Belski
62bdf2b209 block: qcow: Make the qcow header tests positional
Convert the qcow header test helpers to positional access instead of
the AlignedFile cursor. Test only change.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-03 20:29:52 +00:00
Anatol Belski
8c068fce5e block: qcow: Make the qcow raw file tests positional
Convert the QcowRawFile unit test verifications to positional access
instead of the cursor. Test only change.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-03 20:29:52 +00:00
Anatol Belski
aa4eb943f3 block: qcow: Access the qcow header feature bits positionally
Convert the qcow header feature bit writes to positional access and
drop the now unused Seek imports. The result is unchanged.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-03 20:29:52 +00:00
Anatol Belski
43682e22b0 block: qcow: Access the qcow header positionally
Convert the qcow header reader and writer to positional access instead
of the AlignedFile Read, Write, and Seek cursor. The writes still go
through the AlignedFile O_DIRECT bounce, so alignment is unchanged.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-03 20:29:52 +00:00
Adel-Ayoub
4abdb3419b block: Make the sparse-file size test portable across filesystems
zfs and virtiofs do not eagerly account a mode-0 fallocate() in
st_blocks, so detect those filesystems with fstatfs() and skip the
physical-size assertions there. A skip now names a proven platform
limitation instead of being inferred from the value under test, and
every assertion still runs unconditionally on ext4/xfs.

Fixes #8296

Signed-off-by: Adel-Ayoub <adelayoub.maaziz@gmail.com>
2026-07-03 16:27:55 +00:00
Anatol Belski
30fdf384ec block: Test write_unaligned closure error propagation
Add a write side counterpart to read_unaligned_propagates_closure_error.
The test drives write_unaligned with a gather closure that returns an
error and checks that write_unaligned surfaces it unchanged.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-01 22:08:04 +00:00
Anatol Belski
9e441eb899 block: qcow: Add unit tests for detect_image_type
Cover the qcow2 magic and the non qcow magic cases of the AlignedFile
detect_image_type, which now reads the magic positionally.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-01 19:31:16 +00:00
Anatol Belski
3937c224a7 block: qcow: Query backing raw size without the cursor
Opening a raw backing file issued a seek to the end for its size and
then rewound the cursor. RawBacking reads through read_exact_at, so
the cursor reset was dead. Query the size through query_device_size,
matching the crate convention and returning the right size for a
block device backing file as well, and drop the rewind. The now
unused SeekFrom import is removed.

The result is unchanged.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-01 19:31:16 +00:00
Anatol Belski
6a1dee64e7 block: qcow: Read the image magic positionally
detect_image_type saved the cursor, rewound, read the magic, then
restored the cursor. Read the four magic bytes with read_exact_at at
offset 0 and decode with from_be_bytes, so the save, rewind, and
restore go away. BeUint moves to the test module, its only remaining
user in this file.

The result is unchanged.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-01 19:31:16 +00:00
Anatol Belski
abc03f998a block: qcow: Read refcount rebuild markers positionally
The refcount rebuild check issued a seek to the refcount table and to
the first refblock before each cursor read. Read the fixed size fields
with read_exact_at at their offsets and decode with from_be_bytes. The
seeks and the matching error paths go away.

The result is unchanged.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-01 19:31:16 +00:00
Anatol Belski
f893a13af0 block: qcow: Convert metadata cluster I/O to positional
The compressed cluster write and read and the L1 resize size query
went through a seek on the AlignedFile cursor before the access. Pass
the target offset to write_at and read_exact_at, and read the file
length from physical_size.

The result is unchanged. The compressed paths keep routing through the
AlignedFile O_DIRECT bounce.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-01 19:31:16 +00:00
Anatol Belski
a8a3a59544 block: Test the unaligned O_DIRECT bounce helpers
Cover read_unaligned and write_unaligned directly: a scatter read at an
unaligned offset, a short read at EOF, a read-modify-write gather that
preserves head and tail padding, and error propagation from the
scatter closure.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-01 16:27:25 +00:00
Anatol Belski
55b3bad2c3 block: Drop the middle buffer on the unaligned O_DIRECT path
run_unaligned_operation staged every unaligned request in a plain Vec
and then handed it to AlignedFile, which bounced again through an
aligned buffer. That Vec only gave the operation a contiguous range to
scatter into or gather from, which the aligned buffer already is, so
each slow path request paid for an extra allocation and a full length
copy.

Add read_unaligned and write_unaligned on AlignedFile that own the
single aligned bounce and scatter or gather through a closure over the
staging slice. run_unaligned_operation and the FileExt read_at and
write_at impls both route through them, so the staging and
read-modify-write logic lives in one place. The closures keep
AlignedFile free of any AsyncIoOperation dependency.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-01 16:27:25 +00:00
Rob Bradford
f62e2615a9 block: vhdx: Use zerocopy for structs
Replace the use of unsafe struct casting with zerocopy trait derivation.
This fixes a Rust UB where the struct was being filled with a slice of
length greater than the size of the struct.

As a compromise the guid handling was changed to handle the uuids as
opaque bytes as they are mixed endian. This has no impact on the
functionality as they are only used for comparison and has the positive
impact of reducing some of the uuid handling complexity.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-01 12:51:55 +00:00
Anatol Belski
dee6918d7a block: qcow: Add unit tests for qcow refcount positional access
Cover refcount block round trip for the byte aligned and sub byte
paths, and add_cluster_end appending an aligned cluster and staying
within the maximum offset bound.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-30 17:11:35 +00:00
Anatol Belski
56aec72e25 block: qcow: Use physical_size in add_cluster_end
add_cluster_end queried the file length by seeking to the end. Use the
existing physical_size helper instead, which reads the length from the
file metadata. This removes the final cursor access in QcowRawFile, so
the Seek and SeekFrom imports are no longer needed.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-30 17:11:35 +00:00
Anatol Belski
4da6067408 block: qcow: Convert qcow refcount block I/O to positional
The refcount block read and write helpers took a file whose cursor was
positioned by a preceding seek. Pass the target offset down instead and
use positional read_exact_at and write_all_at on the AlignedFile, so
the block methods no longer seek. The byte aligned and sub byte writers
build a buffer and issue one positional write, keeping the previous
batching.

The result is unchanged, as the calls still route through the
AlignedFile O_DIRECT bounce.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-30 17:11:35 +00:00
Daniel Verkamp
494c30be19 block: Avoid unsafe Vec construction in detect_image_type()
The `read_aligned_block_size()` function used `Vec::from_raw_parts()`
incorrectly, causing undefined behavior when deallocating the `Vec<u8>`.

One of the safety invariants of `Vec::from_raw_parts()` is that the
provided pointer must be allocated with the exact same alignment as `T`
(`u8` in this case), but this is clearly not true: `align_of::<u8>()` is
1, but the pointer was allocated with aligment of `blocksize` (typically
512 or greater).

Fix this by using the existing `AlignedFile` helper to read the header
block when probing the image type. This is slightly less efficient than
using `AlignedBuffer` directly, but since this is only called once per
disk image at startup, the difference is probably not worth the extra
verbosity.

Signed-off-by: Daniel Verkamp <drv@meta.com>
2026-06-29 23:26:17 +00:00
Anatol Belski
9c085e11bb block: Add unit tests for qcow raw file positional access
Cover read_pointer_table round trip and masking, and the
write_cluster then zero_cluster round trip, exercising the positional
read_exact_at, write_all_at, and write_all_zeroes_at paths on the
AlignedFile.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-29 21:55:08 +00:00