Compare commits

...

1827 Commits
v50.0 ... v53.0

Author SHA1 Message Date
Rob Bradford
9ed824d6d0 build: Release v53.0
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-12 19:47:13 +00:00
Rob Bradford
58306b6f28 vmm: Support setting seccomp to errno
This will generate -EPERM on seccomp violations as opposed to causing
the VMM to exit with SIGSYS.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-10 21:12:26 +00:00
dependabot[bot]
9c5180fc2c build(deps): bump lycheeverse/lychee-action from 2.8.0 to 2.9.0
Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/lycheeverse/lychee-action/releases)
- [Commits](8646ba3053...e747777578)

---
updated-dependencies:
- dependency-name: lycheeverse/lychee-action
  dependency-version: 2.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 08:00:15 +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
Wei Liu
4fe133d2bd tests: add KDNET over virtio-net integration test
Add a Windows integration test that verifies kernel network debugging
(KDNET) works over a Cloud Hypervisor virtio-net device.

The test boots a Windows guest with a dedicated second virtio-net NIC,
enables KDNET on it via bcdedit (selecting the adapter by the PCI bus
params discovered over SSH), reboots, and then listens on the debugger
host address. Receiving a KDNET poll datagram from the debuggee proves
the whole virtio-net device path works: discovery, feature negotiation,
virtqueue setup and the TX doorbell. No debugger is needed because KDNET
connections are initiated by the target.

Gated to x86-64, where the Windows image ships the virtio-net KDNET
module. The test exercises only the generic virtio-net doorbell path,
so it runs under both KVM and MSHV.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:Opus-4.8
2026-07-09 21:06:20 +00:00
Wei Liu
ab1ba13f7e docs: document KDNET over virtio-net
Windows can run its KDNET kernel-debugging transport over a Cloud
Hypervisor virtio-net device. Add a guide describing how it works.

Link to the new guide from the Windows support document.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:Opus-4.8
2026-07-09 21:06:20 +00:00
Wei Liu
1f487fbc18 docs: split Windows serial debugging into its own file
The Windows serial (COM/KDCOM) kernel debugging instructions lived in a
long subsection of the Windows support document. Move them verbatim
into a dedicated docs/windows-kdcom-debugging.md and leave a short
pointer in windows.md, so the debugging methods can be documented and
extended independently.

Drop the useless disclaimer. Things should work the same across
different Windows versions.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:Opus-4.8
2026-07-09 21:06:20 +00:00
Wei Liu
85c0725f5c virtio-devices: signal queue eventfd for PCI_CFG doorbells
A virtqueue notification (doorbell) is normally delivered to the device
through an ioeventfd registered on the notify address, so a plain MMIO
write to the notify register is consumed by the hypervisor and never
reaches write_bar().

It does reach write_bar() when the driver rings the doorbell through the
VIRTIO_PCI_CAP_PCI_CFG window (write_cap_pci_cfg -> write_bar) instead
of a mapped BAR, or on backends that deliver the write to the VMM such
as SEV-SNP.

The notification arm of write_bar() only re-signalled the matching
queue eventfd under the sev_snp feature and logged an error
otherwise, so a doorbell delivered through the PCI_CFG window was
silently dropped on standard builds and the queue was never
processed.

The virtio spec allows driving the device purely through the PCI_CFG
window, so signal the matching queue eventfd for any doorbell that
reaches write_bar() on every build.

Add unit tests that ring a queue's doorbell via write_bar() and
assert only the addressed queue's eventfd is signalled.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:Opus-4.8
2026-07-09 21:06:20 +00:00
Wei Liu
ae3c517368 scripts: fix Windows test dm/loopback cleanup
The Windows integration test scripts tore down their device-mapper
snapshot and loop devices with 'dmsetup remove_all -f' and 'losetup
-D'. Both operate on every such device on the host, not just the ones
the script created.

On a host whose root filesystem is device-mapper backed (for example an
LVM root), 'dmsetup remove_all -f' replaces the in-use root device's
table with an error target. The host is wedged until reboot.  This is
harmless in CI, which runs in a disposable VM, but destroys a developer
machine.

It turns out windows-snapshot-base is not used at all. Drop it.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:Opus-4.8
2026-07-09 21:06:20 +00:00
Rob Bradford
5ce8732196 vmm: openapi: Correct vmm.nmi endpoint to vm.nmi
This is a VM operation not a VMM operation and was wrongly recorded in
the openapi YAML file.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-09 17:17:41 +00:00
Pulak Kanti Bhowmick
03e229786d vmm: memory_manager: use available_parallelism for prefault threads
Use std::thread::available_parallelism() when sizing the prefault
worker threads so the number of threads scales with the host CPU
count instead of relying on unsafe sysconf.

Fixes: #8495

Signed-off-by: Pulak Kanti Bhowmick <pkbhowmick007@gmail.com>
2026-07-09 11:55:43 +00:00
Rob Bradford
818fc07266 vmm: config: Fix generic vhost-user parsing
The generic vhost-user device took its virtio device type on the
command line via the `virtio_id` parameter, but the same value is
called `device_type` in the API and the resulting config struct. This
irregularity was due to churn during the review process, `device_type`
was the intended name.

Accept `device_type` on the command line and keep `virtio_id` as a
deprecated alias that logs a warning. The alias will then be removed in
a later release.

Fixes: #8545

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-09 10:56:46 +00:00
Muminul Islam
30c0fdaff1 hypervisor: mshv: splice GVA page offset into translated GPA
On Intel MSHV the memory-intercept guest_physical_address and the
MSHV_VP_TRANSLATE_GVA ioctl both return a page-aligned GPA, while
guest_virtual_address is byte-exact. Returning the cached/translated
GPA unchanged made byte-sized MMIO land at BAR offset 0: virtio
device_status writes (BAR+0x14) hit device_feature_select, so
VIRTIO_F_VERSION_1 was never acked and virtio_blk/net/rng probes
failed with -EINVAL, leaving the guest unable to mount rootfs.

Splice gva & 0xfff into the returned GPA on both the intercept fast
path and the translate_gva fallback, and relax the cached-GVA match
to page granularity so it still hits for other byte offsets in the
same page.

This issue is reproducible on Intel machine, launching
Cloud-Hypervisor on nested scenario, using the Linux Dom0
image as the guest image to turn on nested hypervisor
into the guest.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-07-09 03:52:45 +00:00
Rob Bradford
5b53f4202d vmm: Reject removal of already removed devices
A hot-unplug leaves the PCI node in the device tree until the guest
acknowledges the ejection, but VmConfig drops the device entry
immediately.

Move the config removal into DeviceManager::remove_device() and fail
when it returns false, so a second remove-device request cannot reuse
the stale device-tree node.

Assisted-by: OpenAI:Codex-GPT-5
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-08 21:42:49 +00:00
Wei Liu
b2d1065a55 build: disallow building tdx feature
It is broken. There is no use in producing something that doesn't work.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-07-08 20:04:25 +00:00
Alyssa Ross
e8f021b05e vmm: use let/else instead of if/unwrap
There shouldn't have been a safety comment here to begin with, as it's
not unsafe to call unwrap.

Signed-off-by: Alyssa Ross <hi@alyssa.is>
2026-07-08 18:59:36 +00:00
Henry Hrvoje Tonkovac
d897db17e2 vmm: de-duplicate common_thread_rules syscalls
common_thread_rules() holds the syscalls every thread needs for the
SIGSYS violation handler added in #8449: the handler prints a
diagnostic to stderr (write()) after identifying the offending
thread (gettid()).

#8449 added gettid() to common_thread_rules() but left the per-thread
copies in place (9 rule sets still list it, 2 do not). write() is
likewise listed unconditionally in every thread's rule set.

Add write() to common_thread_rules() and drop the per-thread
duplicates of both gettid() and write(). Every thread already allowed
both, so there is no change in behaviour.

write() suggested by @phip1611 in #8490.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-07-08 18:15:37 +00:00
Bo Chen
595a24d270 build: Mark vfio runner as required for MQ
Across the last 20 MQ runs, all 13 vfio runner failures came from two
flaky tests. Both are now skipped and tracked in #8548 and #8549.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-07-08 17:55:35 +00:00
Rob Bradford
19289a3b82 tests: Add integration tests to snapshot after/during restore
Check that we can make a successful snapshot (and restore it) after
another restore. Also check that snapshot it refused until restore is
complete.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-08 17:30:26 +00:00
Rob Bradford
6de90bdec6 vmm: Error out on migration & snapshot if on-demand restoring
If there is an active on-demand restoration then reject any requests to
migrate or snapshot this VM as the memory will not be available for the
snapshot.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-08 17:30:26 +00:00
Rob Bradford
b38ae72a5a vmm: Record when the on-demand memory restore is done
In order to deny migration or snapshot when currently doing an on-demand
restore it is necessary to track whether the prefaulting is completed.
This is a proxy for on-demand restoring being completed as pages that
have been restored by a userfaultfd request will excluded from the
prefault set.

Fixes: #8525

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-08 17:30:26 +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
Bo Chen
b5aeabe77c build: Exclude the qcow2 block tests from the metrics runner
The metrics test list has grown significantly and no longer finishes
even within the 60-minute timeout. Exclude the block_qcow2 group (30
tests) in addition to the micro benchmarks, leaving a 30-test set that
completes meaningfully (~41 minutes in my testing). Trimming and
re-tuning the metrics test list is tracked in #8551.

Since the underlying bare-metal system is also retired, run the metrics
tests on the garm-jammy-16 (Azure VM) runner instead.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-07-08 08:53:57 +00:00
Bo Chen
b353e82085 performance-metrics: Support comma-separated test filters
The '--test-filter' and '--test-exclude' arguments only accepted a
single keyword, even though the underlying selection logic already
matches against a list. Add a comma value delimiter to both so multiple
keywords can be passed in one invocation.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-07-08 08:53:57 +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
Henry Hrvoje Tonkovac
ba3cfd7d9d virtio-devices: restrict vhost/vsock worker socket() to AF_UNIX
The vhost-user (fs, net, block, generic) and vsock worker threads allow
socket() unconditionally in their seccomp rules. These threads only ever
open AF_UNIX sockets: the vhost-user transport connects/binds a Unix
socket (via the vhost crate's Endpoint/Listener), and the vsock device's
host side is a Unix socket. None of them open AF_INET/AF_INET6 sockets.

Restrict their socket() rule to AF_UNIX. Because these threads are
spawned by the VMM thread and inherit its filter, socket() was already
limited to the VMM's set (AF_UNIX/AF_INET/AF_INET6); this narrows it
further to just AF_UNIX, so each worker is confined to what it actually
uses rather than the broader inherited set.

Related to #8490 (giving each thread a more restrictive filter than the
VMM thread).

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-07-07 19:09:15 +00:00
Alyssa Ross
d4660b4fc5 vmm: api: fix parameter name
Fixes: 085a7a49f ("vmm: generic vhost-user: add support")
Signed-off-by: Alyssa Ross <hi@alyssa.is>
2026-07-07 15:58:09 +01:00
Alyssa Ross
43eb6f08a5 vmm: openapi: fix GenericVhostUserConfig properties
Fixes: df86b2864 ("vmm: add HTTP API endpoints for generic vhost-user")
Signed-off-by: Alyssa Ross <hi@alyssa.is>
2026-07-07 14:07:09 +00:00
Anatol Belski
72796d62eb net_util: queue_pair: Use vnet_hdr_len when locating num_buffers
num_buffers sits at offset 10 of the virtio net header, so only
vnet_hdr_len() bytes need to translate contiguously to compute
its host address. Shrink the translate_gva length from desc.len()
to vnet_hdr_len() so the request matches what is actually read.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-07 12:54:03 +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
854bd6bf65 devices: Fix clippy: unneeded late initialization
```
warning: unneeded late initialization
   --> devices/src/legacy/gpio_pl061.rs:258:9
    |
258 |         let value;
    |         ^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
    = note: `-D clippy::needless-late-init` implied by `-D clippy::all`
```

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-07 08:31:22 +00:00
Rob Bradford
52f5d5fc9a pci: Fix clippy: use mem::take instead of drain().collect()
```
warning: you seem to be trying to move all elements into a new `Vec`
   --> pci/src/configuration.rs:951:24
    |
951 |                 return self.pending_bar_reprogram.drain(..).collect();
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `mem::take` to avoid creating a new allocation: `std::mem::take(&mut self.pending_bar_reprogram)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#drain_collect
    = note: `-D clippy::drain-collect` implied by `-D clippy::all`
```

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-07 08:31:22 +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
Wei Liu
2a3512fb71 docs: update SEV-SNP build information
sev_snp no longer selects a hypervisor backend implicitly.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-07-06 23:29:52 +00:00
Wei Liu
47e88d26e5 build: decouple igvm and sev_snp from mshv
igvm and sev_snp only need the MSHV backend when a caller selects that
backend. Stop enabling mshv implicitly from those top-level features so
KVM SEV-SNP builds do not compile unused MSHV backend code.

Require igvm and sev_snp to be built with kvm or mshv. Backend-less
configurations having no runtime hypervisor are not supported.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-07-06 23:29:52 +00:00
Wei Liu
8f38928757 ci: cover explicit igvm sev_snp backends
Decoupling removes the old implicit MSHV coverage from standalone igvm
and sev_snp jobs. Replace those jobs with explicit KVM feature sets and
add MSHV igvm and sev_snp build and clippy jobs so both backends remain
covered.

This keeps the previous implicit combinations tested while making the
selected backend visible in CI.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-07-06 23:29:52 +00:00
Wei Liu
d71ef33b83 vmm: decouple igvm loader from mshv
IGVM no longer implies the MSHV backend at the feature layer. Gate the
loader paths that use MSHV page types, MSHV SNP launch defaults, or
MSHV-specific CPUID page rewriting on the MSHV feature.

This preserves existing MSHV behavior while letting KVM SEV-SNP use the
shared IGVM loader without compiling the full MSHV backend.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-07-06 23:29:52 +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
Rob Bradford
4b120c3702 virtio-devices: Gate VIRTIO_NET_S_ANNOUNCE on feature acked
Only set the VIRTIO_NET_S_ANNOUNCE status bit if the feature
VIRTIO_NET_F_GUEST_ANNOUNCE was acknowledged.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-06 18:46:56 +00:00
Rob Bradford
9161b18f9b virtio-devices: iommu: Translate buffers spanning adjacent mappings
The virtio-iommu translation required the whole [addr, addr+size) span
to be covered by a single mapping. A guest is free to describe one
contiguous buffer with several adjacent mappings: the specification lets
the driver map at page granularity and the Linux IOMMU core splits a
single mapping request at page size boundaries. A descriptor buffer
backed this way was rejected as an invalid translation even though every
page was mapped, wedging the device.

Walk consecutive mappings when no single one covers the span, accepting
the translation once the mappings are adjacent in IOVA space and
contiguous in guest-physical space. A non-contiguous span cannot be
represented by the single returned address and is still rejected.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-06 18:35:11 +00:00
Anirudh Rayabharam
0b150ea560 tests: add integration test for PPTT cache topology
Add an integration test to verify the newly added code to expose
cache topology information in PPTT.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-07-06 16:31:21 +00:00
Anirudh Rayabharam
ec73733b21 vmm: add cache topology info to ACPI PPTT
Read the host cache info from sysfs and pass through the same to the
guest via the PPTT table. This is the same as the approach taken for
FDT. Similar to that, assume that the L3 cache is always shared and the
L2 cache is unique per CPU.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-07-06 16:31:21 +00:00
Anirudh Rayabharam
9446dad1d0 arch: aarch64: extract cache topology collection into a helper
Both arch/src/aarch64/fdt.rs and vmm/src/cpu.rs declared the same
~14 cache info locals (size/line_size/sets for L1D/L1I/L2/L3, plus
shared flags for L2/L3), checked for /sys/devices/system/cpu/cpu0/cache,
and populated those locals via get_cache_size /
get_cache_coherency_line_size / get_cache_number_of_sets /
get_cache_shared. Two near-identical ~30-line blocks.

Add a CacheTopologyInfo struct in arch/src/aarch64/cache.rs whose
field names mirror the existing locals, plus a read_cache_topology()
helper that returns None when the sysfs hierarchy is missing and
only queries get_cache_shared for L2/L3 when their size is non-zero
(preserving current behavior).

Both call sites now obtain the info via read_cache_topology() and
destructure it back into locals of the same name, so all downstream
references are unchanged.

Assisted-by: Copilot-CLI:Claude-Opus-4.7
Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-07-06 16:31:21 +00:00
Anirudh Rayabharam
c30732c9c1 arch: use bit-shift expressions for cache size units
Replace 1024, 1024u32.pow(2), and 1024u32.pow(3) with 1u32 << 10,
1u32 << 20, and 1u32 << 30 in get_cache_size. The shift form makes
the binary (KiB/MiB/GiB) nature of the conversion immediately
obvious and is easier to read at a glance.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-07-06 16:31:21 +00:00
Anirudh Rayabharam
2bb02d51ed arch: aarch64: move cache helpers to a new file
fdt.rs has helper functions to query host cache details (topology, size
etc.). Extract these helpers to a new file cache.rs so that they can be
used for PPTT construction as well.

No functional change.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-07-06 16:31:21 +00:00
Anirudh Rayabharam
f5089c705b vmm: use acpi_tables helpers for PPTT
Use the helpers from the acpi_tables crate to construct the PPTT. This
is in preparation for adding cache hierarchy info to the PPTT which is
simpler using the helpers.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-07-06 16:31:21 +00:00
doge
3d5f06ff03 vmm: acpi: provide FADT PM1a event/control blocks for nested Hyper-V
A Windows guest that launches nested Hyper-V (for example to run WSL2)
fails to start its hypervisor on cloud-hypervisor's HW-reduced-ACPI
FADT. hvloader's hypervisor-launch path (0x18000f01c -> 0x180015628 ->
0x180015788) registers every legacy PM register block via 0x1800158dc
and rejects any block whose GAS address is 0 with status 8
(STATUS_INVALID_DEVICE_REQUEST). hvix64 then never launches and
HypervisorPresent stays False. The HW-reduced FADT leaves those blocks
zero.

Emit valid PM1a event/control blocks (I/O ports, lengths and X_GAS) in
the FADT and reserve those ports in the I/O allocator so nothing else
claims them. The HW-reduced guest OS ignores the legacy ports; only
hvloader's ACPI validation reads them.

These blocks are only useful to a guest that itself runs an enlightened
hypervisor, so emit them only when both guest nesting and the Hyper-V
enlightenments are enabled (--cpu nested=on,kvm_hyperv=on).

Signed-off-by: doge <me@crackerben.com>
2026-07-06 14:42:11 +00:00
Gauthier Jolly
5b3416adf4 ci: Add riscv64 cross-build check
The previous CI (removed in #7760) built natively inside a riscv64 QEMU
VM over SSH on a self-hosted host. That setup was disabled for chronic
infrastructure flakiness (#7758).

Reintroduce coverage deliberately minimal: cross-compile the
cloud-hypervisor binary on a stock ubuntu-latest runner, and nothing
more.

- No integration tests: no riscv64 hardware needed.
- No clippy: cross clippy with -D warnings currently trips on
  riscv64-gated lints that need a separate cleanup commit first.

Cross-compiling needs only a riscv64 linker, not Docker/cross: the
binary does not pull openssl-sys (that arrives via a dev-dependency of
test_infra, unused by -p cloud-hypervisor), so no cross sysroot is
required.

Signed-off-by: Gauthier Jolly <contact@gjolly.fr>
2026-07-06 14:41:40 +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
4596c502fb vhost_user_block: Query the disk size instead of seeking
The disk size in sectors was computed by seeking the AlignedFile to the
end. Use query_device_size instead, which also handles block devices,
and drop the now unused Seek and SeekFrom imports.

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
Gauthier Jolly
c257cdd695 vmm: Gate tpm import for riscv64
The `tpm` module in the `devices` crate is disabled on riscv64 (see
commit 0042447fb "devices: Disable tpm module for riscv64"), and every
use of `tpm` in the device manager is already gated behind
`#[cfg(not(target_arch = "riscv64"))]`. However, the import itself was
merged into the unconditional `use devices::{...}` line in commit
025e782e5 "vmm: trim qualified paths", which broke the riscv64 build:

    error[E0432]: unresolved import `devices::tpm`
      --> vmm/src/device_manager.rs:70:83

Split the `tpm` import out into its own line gated with
`#[cfg(not(target_arch = "riscv64"))]`, matching all of its usages.

Signed-off-by: Gauthier Jolly <contact@gjolly.fr>
2026-07-06 07:24:56 +00:00
Wei Liu
de4ff39f4d pci: align VFIO MMIO mmap to a huge page boundary
mmap(NULL, ...) only guarantees page-sized alignment, so the device
MMIO regions mapped for VFIO passthrough were not necessarily aligned
on a huge page boundary and thus could not take fast paths in the
kernel.

Align the address to the largest possible size. This allows VFIO to take
fast paths when walking the page table.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:Opus-4.8
Assisted-by: Copilot:GPT-5.5
2026-07-05 17:08:50 +00:00
Rob Bradford
14481800e3 build: Only run MSHV integration tests on MQ
There is a single runner for this so avoid overloading/queueing by
running only on MQ.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-04 18:42:59 +00:00
Anatol Belski
cbef28e5cd ci: Switch to Windows Server 25H2 for AARCH64
The updated image is configured in a same way as before

SSH and RDP are enabled.

Includes latest stable virtio-win 0.1.285 drivers.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-07-04 18:42:06 +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
Sebastian Eydam
4ac2340c1f virtio-devices: net: offer VIRTIO_NET_F_GUEST_ANNOUNCE to guests
The virtio-net guest announce feature bit was accidentally dropped
during upstreaming, so the device never advertised it to guests. Restore
the feature offer for virtio-net.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-07-03 13:47:11 +00:00
dependabot[bot]
629151481b build(deps): bump the non-rust-vmm group across 2 directories with 18 updates
Bumps the non-rust-vmm group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.102` | `1.0.103` |
| [env_logger](https://github.com/rust-cli/env_logger) | `0.11.10` | `0.11.11` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.29` | `0.2.31` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.3` | `1.23.4` |
| [crc-any](https://github.com/magiclen/crc-any) | `2.5.0` | `2.5.1` |
| [io-uring](https://github.com/tokio-rs/io-uring) | `0.7.12` | `0.7.13` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.9.1` | `1.9.2` |
| [chacha20](https://github.com/RustCrypto/stream-ciphers) | `0.10.0` | `0.10.1` |
| [debug-helper](https://github.com/magiclen/debug-helper) | `0.3.13` | `0.3.14` |
| [hashbrown](https://github.com/rust-lang/hashbrown) | `0.17.0` | `0.17.1` |
| [hybrid-array](https://github.com/RustCrypto/hybrid-array) | `0.4.12` | `0.4.13` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.102` | `0.3.103` |

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

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.102` | `1.0.103` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.3` | `1.23.4` |
| [crc-any](https://github.com/magiclen/crc-any) | `2.5.0` | `2.5.1` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.9.1` | `1.9.2` |
| [chacha20](https://github.com/RustCrypto/stream-ciphers) | `0.10.0` | `0.10.1` |
| [debug-helper](https://github.com/magiclen/debug-helper) | `0.3.13` | `0.3.14` |
| [hybrid-array](https://github.com/RustCrypto/hybrid-array) | `0.4.12` | `0.4.13` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.102` | `0.3.103` |



Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `env_logger` from 0.11.10 to 0.11.11
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.10...v0.11.11)

Updates `jiff` from 0.2.29 to 0.2.31
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.29...jiff-static-0.2.31)

Updates `uuid` from 1.23.3 to 1.23.4
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.3...v1.23.4)

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

Updates `io-uring` from 0.7.12 to 0.7.13
- [Commits](https://github.com/tokio-rs/io-uring/compare/v0.7.12...v0.7.13)

Updates `arc-swap` from 1.9.1 to 1.9.2
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/commits)

Updates `chacha20` from 0.10.0 to 0.10.1
- [Commits](https://github.com/RustCrypto/stream-ciphers/compare/chacha20-v0.10.0...chacha20-v0.10.1)

Updates `debug-helper` from 0.3.13 to 0.3.14
- [Commits](https://github.com/magiclen/debug-helper/compare/v0.3.13...v0.3.14)

Updates `env_filter` from 1.0.1 to 2.0.0
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/env_filter-v1.0.1...env_filter-v2.0.0)

Updates `hashbrown` from 0.17.0 to 0.17.1
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.17.0...v0.17.1)

Updates `hybrid-array` from 0.4.12 to 0.4.13
- [Changelog](https://github.com/RustCrypto/hybrid-array/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/hybrid-array/compare/v0.4.12...v0.4.13)

Updates `jiff-static` from 0.2.29 to 0.2.31
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.29...jiff-static-0.2.31)

Updates `js-sys` from 0.3.102 to 0.3.103
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `wasm-bindgen` from 0.2.125 to 0.2.126
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.125...0.2.126)

Updates `wasm-bindgen-macro` from 0.2.125 to 0.2.126
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.125...0.2.126)

Updates `wasm-bindgen-macro-support` from 0.2.125 to 0.2.126
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.125...0.2.126)

Updates `wasm-bindgen-shared` from 0.2.125 to 0.2.126
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.125...0.2.126)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `uuid` from 1.23.3 to 1.23.4
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.3...v1.23.4)

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

Updates `arc-swap` from 1.9.1 to 1.9.2
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/commits)

Updates `chacha20` from 0.10.0 to 0.10.1
- [Commits](https://github.com/RustCrypto/stream-ciphers/compare/chacha20-v0.10.0...chacha20-v0.10.1)

Updates `debug-helper` from 0.3.13 to 0.3.14
- [Commits](https://github.com/magiclen/debug-helper/compare/v0.3.13...v0.3.14)

Updates `hybrid-array` from 0.4.12 to 0.4.13
- [Changelog](https://github.com/RustCrypto/hybrid-array/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/hybrid-array/compare/v0.4.12...v0.4.13)

Updates `js-sys` from 0.3.102 to 0.3.103
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `wasm-bindgen` from 0.2.125 to 0.2.126
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.125...0.2.126)

Updates `wasm-bindgen-macro` from 0.2.125 to 0.2.126
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.125...0.2.126)

Updates `wasm-bindgen-macro-support` from 0.2.125 to 0.2.126
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.125...0.2.126)

Updates `wasm-bindgen-shared` from 0.2.125 to 0.2.126
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.125...0.2.126)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: env_logger
  dependency-version: 0.11.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.23.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: crc-any
  dependency-version: 2.5.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: io-uring
  dependency-version: 0.7.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.9.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: chacha20
  dependency-version: 0.10.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: debug-helper
  dependency-version: 0.3.14
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: env_filter
  dependency-version: 2.0.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: hashbrown
  dependency-version: 0.17.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: hybrid-array
  dependency-version: 0.4.13
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.31
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.103
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.126
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.126
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.126
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.126
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.23.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: crc-any
  dependency-version: 2.5.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.9.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: chacha20
  dependency-version: 0.10.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: debug-helper
  dependency-version: 0.3.14
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: hybrid-array
  dependency-version: 0.4.13
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.103
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.126
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.126
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.126
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.126
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-03 00:55:56 +00:00
dependabot[bot]
43d97b0546 build(deps): bump dorny/paths-filter from 4.0.1 to 4.0.2
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](fbd0ab8f3e...7b450fff21)

---
updated-dependencies:
- dependency-name: dorny/paths-filter
  dependency-version: 4.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-03 00:37:37 +00:00
Rob Bradford
6f815c4546 vmm: config: Check NetConfig::socket set if vhost_user is set
This check was already included in the DiskConfig validation but missing
for NetConfig.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-02 15:35:39 +00:00
Rob Bradford
e9ee46f62b vmm: config: Reject invalid virtio queue sizes
The queue size must fit into a u16 and be a power of 2 according to the
spec.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-02 15:35:39 +00:00
Rob Bradford
7c7fe7091c vmm: config: Replace integer list conversion
Now that IntegerList can handle any width number type use that for the
queue_sizes parameter on `--generic-vhost-user`

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-02 15:35:39 +00:00
Rob Bradford
1a441fb7ed option_parser: Make IntegerList generic
This allows its use for all integer types and will generate an error if
the value is too wide for the type.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-02 15:35:39 +00:00
Philipp Schuster
da80fde887 vmm: streamline printing of error chains
In [0] we agreed on the current format.

- Use `: ` over ` => `
- Streamline usages in a helper

Context https://github.com/cloud-hypervisor/cloud-hypervisor/issues/8510

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7066

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-07-02 15:24:20 +00:00
Sebastian Eydam
53a00c0514 virtio-devices: net: add guest-announce plumbing
Advertise `VIRTIO_NET_F_GUEST_ANNOUNCE` on virtio-net devices, surface
`VIRTIO_NET_S_ANNOUNCE` through config status, and handle
`VIRTIO_NET_CTRL_ANNOUNCE_ACK` on the control queue.

This adds the guest-visible state needed for post-migration or
post-restore announce requests; the VMM side triggering is added in
follow-up commits.

The motivation is to reduce post-migration and post-restore
connectivity gap. After a live migration or after restoring, it can
take the guest several seconds to be reachable again over the network.
With these announcements, the network path should be refreshed within a
few milliseconds.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-07-02 15:17:40 +00:00
Sebastian Eydam
e17c0be127 virtio-devices: net: report link up in config status
Expose `VIRTIO_NET_S_LINK_UP` through the virtio-net config status field
when `VIRTIO_NET_F_STATUS` was negotiated.

This makes the guest-visible status bits reflect the device runtime
state and prepares the config status path used by later post-migration
announce handling.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-07-02 15:17:40 +00:00
Saravanan D
f5967cf287 docs: Document VFIO snapshot and restore support
Add a Snapshot and Restore section to docs/vfio.md covering the
migration v2 requirements (Linux 5.18 kernel, variant VFIO
driver such as mlx5_vfio_pci) and the restore sequence for
devices that advertise migration v2.

The behavior description covers the full restore sequence. It
documents the RUNNING to RESUMING single transition (the kernel
walks the intermediate STOP arc), the post load PCI_COMMAND push
to the device, and the MSI or MSI-X eventfd rearm that the
kernel state does not carry. Behavior matches QEMU
vfio_pci_load_config().

It notes one limitation, the snapshot format stores the opaque
device blob as base64 inside the snapshot JSON, which may benefit
from a binary transport path for very large state.

docs/snapshot_restore.md replaces its VFIO out of scope
limitation with a short section that points to docs/vfio.md for
the requirements and behavior.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-07-02 01:13:03 +00:00
Saravanan D
8b31602650 pci: vfio: Add unit tests for migration state machine
The VFIO save and load paths drive the kernel migration state machine
through a strict transition sequence and must recover correctly when a
transition or the data transfer fails. Validating that on real hardware
needs a migratable device, which CI does not have, so the behavior would
otherwise go unverified until it breaks in the field.

Introduce a mock Vfio wrapper that records state transitions and keeps
the migration blob in memory, then test VfioCommon save and load against
it. This pins the transition ordering, the data round trip, and the
failure recovery in CI on any host, independent of hardware. The trait
defaults and the VfioMigrationState conversions are covered too, so a
non migratable device and an unknown state value stay well defined. A
restore that carries migration state onto a device without migration
support is checked to fail instead of dropping the state.

A shadow sync test guards the related save path change, confirming a non
BAR config write reaches the PciConfiguration shadow so a snapshot
captures the live value instead of the post init zero.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-07-02 01:13:03 +00:00
Saravanan D
4102768561 pci: vfio: Implement restore path state transitions
When a snapshot is loaded, walk the migration v2 state machine
from VfioCommon::set_state() after interrupt state has been
restored. If the device supports migration and a blob is
present, drive RUNNING to RESUMING in a single transition and
write the blob to the data_fd. The kernel handles the
intermediate STOP arc internally. An explicit STOP dwell was
observed to make mlx5_vfio_pci re initialize SQ, CQ, and EQ
indices on top of the just loaded blob, wedging queue state on
resume. The device is left in RESUMING and resume() drives it
to RUNNING during VM resume.

set_state() also pushes PCI_COMMAND to the device via
write_config() after the blob load. Rebuilding the in memory
MSI or MSI-X structs does not touch the kernel's view of
PCI_COMMAND, so without this the VF sits at post reset defaults
with no bus master and mlx5_core ACCESS_REG times out. It
rearms VFIO_DEVICE_SET_IRQS via enable_msi or enable_msix for
the same reason, since replaying the in memory interrupt state
does not reissue the ioctl and the kernel has no eventfds for
this device until it does. Both match QEMU
vfio_pci_load_config().

In allocate_bars, skip add_pci_bar and add_pci_rom_bar on
restore. PciConfiguration::new(Some(state)) already populated
the BAR registers with used=true, so the extra call trips
BarInUse. The bars vec and mmio_regions pushes still need to
happen so the caller can wire bus mappings.

set_state() retrieves the migration blob from the snapshot
unconditionally and rejects a snapshot that carries migration
state when the device does not support migration, rather than
silently dropping the saved state. A device without migration
support and no blob, including vfio-user, still skips the load.

On any transition or write failure during restore, STOP is
attempted as best effort before bubbling the error.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-07-02 01:13:03 +00:00
Saravanan D
e7c0d690d0 pci: vfio: Implement save path state transitions
Wire a migratable VFIO device's migration state to the VM lifecycle so
the device's internal state survives snapshot and restore. A device such
as a ConnectX VF bound to mlx5_vfio_pci would otherwise come back blank,
because a plain snapshot saves only the PCI configuration Cloud
Hypervisor owns, not the device's own state.

On save, pause moves the device to STOP and snapshot() drives it through
STOP_COPY to extract the opaque state blob, attached to the device
snapshot as a base64 encoded child. resume() returns it to RUNNING.

All new behavior is gated on migration_flags.is_some(), so devices
without migration support (including vfio-user) retain their previous
snapshot behavior.

If the data read fails after STOP_COPY was entered, the device is
returned to STOP before the error is bubbled, since the STOP_COPY
to STOP arc stays valid. A failed transition into STOP_COPY returns
immediately because a STOP from the resulting ERROR state cannot
help. Full recovery including device reset is deferred.

Since the non BAR write path goes directly to the VFIO device and not
the shadow, the PciConfiguration shadow can get stale. Mirror every
non BAR, non MSI config write into the shadow via write_byte /
write_word / write_reg so snapshot() can capture PCI_COMMAND. Without
this the shadow keeps the values set at device init and snapshot()
encodes PCI_COMMAND as zero.

Use the raw write_byte, write_word, and write_reg helpers rather than
PciConfiguration::write_config_register, which would otherwise drain
pending_bar_reprogram, consumed by the BAR block below, and rerun
MSI-X set_msg_ctl, already done by update_msix_capabilities.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-07-02 01:13:03 +00:00
Saravanan D
41ffd04644 pci: vfio: Probe migration v2 capabilities
Probe VFIO_DEVICE_FEATURE_MIGRATION during VfioCommon::new() and store
the result in a new migration_flags field so later migration phases can
gate state machine transitions.

The probe runs on every instantiation, including snapshot restore,
because migration capability is a property of the host kernel and its
variant driver rather than of any saved VM state.

migration_flags() is added to the internal Vfio trait with a default
implementation that returns Ok(None), meaning not migratable.
VfioDeviceWrapper overrides it to issue the kernel ioctl, while
vfio-user devices keep the default and are always treated as non
migratable.

Allow the VFIO_DEVICE_FEATURE ioctl in the VMM seccomp filter.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-07-02 01:13:03 +00:00
dependabot[bot]
f5344414e9 build(deps): bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 00:43:24 +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
Rob Bradford
eb838144f6 vmm: memory_manager: Check MMIO access size is correct
Check that the MMIO accesses is 4 bytes long as otherwise it would
be possible for the guest to trigger a panic when the memory ranges base
and length are copied for fulfilling the MMIO read.

This pattern of check matches similar checks in CpuManager and
DeviceManager.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-01 17:29:19 +00:00
Rob Bradford
c5104a9f17 devices: fw_cfg: Correctly handle short and long reads
Fill the target MMIO buffer with zeroes to handle reads with access
sizes larger than the data and also check that the read access length
does not exceed the size of the backing slice (previously it just
checked the access size vs length not taking the offset into account).

Assisted-by: Claude:Opus-4.8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-01 17:22:36 +00:00
Tushar Khatri
5f5de2ebca docs: clarify log level guidance in logging.md
Broaden error!() to cover any user-initiated action that fails to do
what was expected (e.g. failed hotplug or live migration), not only
unrecoverable startup errors. Retarget info!() at operators and users,
clarify the warn!() and debug!() audiences, and document trace!().

Part of #8440.

Co-authored-by: Philipp Schuster <phip1611@gmail.com>
Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-07-01 16:45:38 +00:00
Henry Hrvoje Tonkovac
ae7c0dd9f1 build: exempt arch from clippy::absolute_paths, use full paths
Suggested by phip1611 on #8446.

This adds the repo's first clippy.toml, carving arch out of the
absolute_paths deny from #7670. Glob imports and trait imports that
must be in scope for method-call resolution (e.g. DeviceInfoForFdt for
.irq()) are left as-is.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-07-01 16:27:46 +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
Rob Bradford
f5f7b092e1 arch: smbios: Avoid unsafe slice::from_raw_parts()
The struct already implements ByteValued so this unsafe block can be
changed to call as_slice() from that trait.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-01 10:44:10 +00:00
Rob Bradford
330f2fda1c arch: mptable: Avoid unsafe slice::from_raw_parts()
The struct already implements ByteValued so this unsafe block can be
changed to call as_slice() from that trait.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-07-01 10:44:10 +00:00
Aastha Rawat
efedfb47e9 ci: consolidate mshv integration tests into ci.yaml
Migrate the MSHV integration tests to run natively on the self-hosted
runner instead of spinning up a separate VM. This simplifies the
workflow pipeline & mitigates Azure capacity issues.

Signed-off-by: Aastha Rawat <aastharawat@microsoft.com>
2026-07-01 08:23:41 +00:00
dependabot[bot]
b72a1cd6b4 build(deps): bump crate-ci/typos from 1.47.2 to 1.48.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.47.2 to 1.48.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](37bb98842b...bee27e3a4f)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-01 00:41:35 +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
Philipp Schuster
fa5c2cc562 docs: scope testing container overview
Make the testing overview describe the dev_cli.sh workflow instead of
implying that every Cloud Hypervisor build must run in a container.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-30 17:03:30 +00:00
Philipp Schuster
f508e6b1bb docs: remove stale testing TOC entry
Drop the CI workflows link from testing.md because the document has no
matching section.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-30 17:03:30 +00:00
Philipp Schuster
d0cf0a4e20 docs: clarify D-Bus API names
Distinguish the configured D-Bus service name from the fixed DBusApi1
interface name in the API documentation.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-30 17:03:30 +00:00
Philipp Schuster
00a713e5ba docs: update CPU option examples
Use the structured --cpus boot= syntax in cloud-hypervisor launch
examples so they match the current VM config parser.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-30 17:03:30 +00:00
Philipp Schuster
cc6426d87c docs: fix migration command examples
Use the keyed receiver_url and destination_url parameters expected by
ch-remote's current migration parsers [0].

[0] 6fa044d101

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-30 17:03:30 +00:00
Philipp Schuster
2e62081bec docs: update SEV-SNP backend support
Remove the stale MSHV-only wording now that the docs and CVM test path
cover KVM SEV-SNP with IGVM stage0 and fw_cfg [0, 1].

[0]: https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7942
[1]: https://github.com/cloud-hypervisor/cloud-hypervisor/pull/8347

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-30 17:03:30 +00:00
Philipp Schuster
013981b649 vm-migration: improve debuggability on receiver for failed migrations
We cannot reliably send Request::abandon() on every kind of failure on
the sender side, as we might be in the middle of a memory transmission.
The receiver would not reliably know what to do with that. So instead,
when the receiver cannot read from the socket, we log that the migration
sender failed, which is the only likely cause of that failure.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-30 16:59:20 +00:00
Philipp Schuster
81022ab087 vm-migration: improve logging to make clear if receiver caused failure
This improves the observability whether a migration failed because of
the sender or because of some error on the receiving side.

Using a simple log message is simpler than introducing a new error enum
to differentiate between SendError and RemoteError.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-30 16:59:20 +00:00
Sebastian Eydam
969d2f855d vm-migration: stop sending abandon after error response
When the receiver of a live migration encounters an error, it sends an
error response. The sender of the migration would then send an abandon
request and wait for a response. This abandon request is not necessary,
because the receiver already abandoned the migration due to the error it
encountered.

From now on this function will not send an abandon request to the
receiver anymore, thus it was renamed to "ok_or_error".

Also, this case was always broken, because after sending the error
response, the receiver just exits without waiting for the additional
abandon request.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-30 16:59:20 +00:00
Philipp Schuster
f35c9842be docs: update new prefault behavior in memory.md
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-30 16:56:42 +00:00
Sebastian Eydam
22cf10b6ab vmm: return error if prefaulting fails
Prefaulting pages was done on a best-effort basis before, meaning that
errors were ignored. This could lead to errors during runtime,
especially when used with hugepages, because there was no guarantee that
enough pages are available. With this change errors during prefaulting
will be reported.

Co-authored-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-30 16:56:42 +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
ab593accb3 virtio-devices: Test device config capability presence
Add unit tests for add_pci_capabilities covering the configless
device path. The device config capability is present when the
config region is sized and absent when the size is zero.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-29 23:09:52 +00:00
Anatol Belski
3c90f91491 virtio-devices: Report no config space for configless devices
The watchdog, rng, and rtc devices expose no device specific
configuration fields. Each now reports a config size of zero so the
transport omits the device configuration capability instead of
advertising an unbacked region that the device cannot service.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-29 23:09:52 +00:00
Anatol Belski
d1dd6b13d5 virtio-devices: Size the device config capability per device
Add a config_size method to VirtioDevice and use it when building the
PCI device configuration capability. The transport advertises the size
reported by the device and omits the capability entirely when the size
is zero, because the virtio driver rejects a zero length capability.

The method defaults to None, so every device keeps its current
capability size.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-29 23:09:52 +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
Anatol Belski
833b360e18 block: Convert qcow raw file table and cluster writes to positional
Replace the seek then read/write metadata access in QcowRawFile with
positional read_exact_at, write_all_at, and write_all_zeroes_at on the
AlignedFile. read_pointer_table, write_pointer_table,
write_pointer_table_direct, zero_cluster, and write_cluster no longer
move the file cursor.

These calls still route through AlignedFile, which implements FileExt
and WriteZeroesAt with the O_DIRECT alignment bounce, so the unaligned
behavior is preserved. Each access already issued an absolute seek
before touching the file, so the cursor never carried state between
calls and dropping it is unobservable.

Decoding the pointer table now uses native from_be_bytes over the read
buffer, matching the to_be_bytes path on the write side.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-29 21:55:08 +00:00
Henry Hrvoje Tonkovac
fe8bd6e62c block: fix VHDX region table overlap detection
The region table overlap check in RegionInfo::new only rejected a new
region that strictly engulfed an existing one. Identical, fully
contained, and partially overlapping regions passed undetected, so a
malformed VHDX with overlapping region entries was wrongly accepted.

Per [MS-VHDX] all region objects MUST be non-overlapping, so such an
image should be rejected. Replace the faulty predicate with a correct
half-open interval overlap test, extracted into a small pure helper
(ranges_overlap).

Add a unit test for the predicate and an integration test that feeds a
crafted region table with two overlapping entries through the real
RegionInfo::new, confirming it is now rejected with RegionOverlap.

Related to #8009 (broader VHDX overlap validation).

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-29 21:51:12 +00:00
Philipp Schuster
19e67a2c7e vmm: streamline migration related API error variants
Receiving a migration happens inside the VMM thread, which blocks the
API until a migration was received. On the other hand, sending a
migration is actually just a dispatch operation. We adjust the wording
to improve clarity of the error messages.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-29 18:07:51 +00:00
CMGS
f8d0186a26 arch: x86_64: recommend Hyper-V paravirt TLB flush and cluster IPI
Adds three recommendation bits to CPUID 0x40000004.EAX so Windows /
Hyper-V-aware guests use the corresponding paravirtualized hypercalls
instead of falling back to architectural primitives. The hypercalls
themselves are emulated unconditionally by KVM and surfaced via the
corresponding KVM_CAP_HYPERV_* info caps; no userspace cap negotiation
is needed because KVM advertises support to the guest at hypercall
issue time:

  KVM_CAP_HYPERV_TLBFLUSH advertises HvFlush{VirtualAddressSpace,Ex,
                          List,ListEx} (api.rst 8.18, info-only cap).
  KVM_CAP_HYPERV_SEND_IPI advertises HvCallSendSyntheticClusterIpi{,Ex}
                          (api.rst 8.20, also info-only).

Leaf 0x40000004.EAX (HV_CPUID_ENLIGHTMENT_INFO):
  bit 1  LocalTlbFlushRecommended
  bit 2  RemoteTlbFlushRecommended
         Recommend HvFlushVirtualAddressSpace / List in place of
         architectural INVPCID / INVLPG broadcasts. Remote shoot-down
         via hypercall lets the host skip vCPUs that are not currently
         scheduled, instead of waiting for an IPI ack.
  bit 10 ClusterIpiRecommended
         Recommend HvCallSendSyntheticClusterIpi over per-target APIC
         ICR writes. A single hypercall can target up to 64 vCPUs (or
         all of them via the Ex variant) versus one VM exit per APIC
         access on the architectural path.

These bits depend on AccessVpIndex (0x40000003.EAX bit 6), which is
advertised by the partition-privileges change.

Sources:
  Microsoft Hypervisor Top-Level Functional Specification 7.4.5
  qemu/qemu docs/system/i386/hyperv.rst (hv-tlbflush, hv-ipi)
  Linux Documentation/virt/kvm/api.rst 8.18, 8.20

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-06-29 18:03:44 +00:00
CMGS
ba11523760 arch: x86_64: advertise additional Hyper-V CPUID enlightenments
Extends the Hyper-V partition feature CPUID leaf 0x40000003 with bits
that KVM emulates unconditionally and that Windows / Hyper-V-aware
guests consult to skip slow fallback paths. No KVM capability
negotiation is required for any of these -- they are hints to the
guest about what is already legal to use.

Leaf 0x40000003.EAX (HV_CPUID_FEATURES):
  bit 0  AccessVpRuntimeReg     -- HV_X64_MSR_VP_RUNTIME (0x40000010)
  bit 4  AccessIntrCtrlRegs     -- HV_X64_MSR_{EOI,ICR,TPR,APIC_ASSIST}
  bit 11 AccessFrequencyMsrs    -- HV_X64_MSR_{TSC,APIC}_FREQUENCY (skips
                                   guest TSC/APIC calibration loops)

Leaf 0x40000003.EDX (HV_CPUID_FEATURES, TLFS rev 6.0c):
  bit 4  FastHypercall          -- HV_HYPERCALL_PARAMS_XMM_AVAILABLE
  bit 8  ExtendedGvaRangesForFlushVirtualAddressList -- pairs with the
                                   tlbflush-ext recommendation bit

AccessHypercallMsrs (bit 5) and AccessVpIndex (bit 6) are already
advertised by the partition-privileges change.

Sources:
  Microsoft Hypervisor Top-Level Functional Specification 7.4.{2,5}
  qemu/qemu docs/system/i386/hyperv.rst (hv-vapic, hv-frequencies,
                                          hv-vpruntime)

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-06-29 18:03:44 +00:00
Sebastian Eydam
aa629a1485 vmm: make aborted migrations on receiver side return an error
On the receiver side, a live migration with status "aborted" does not
return an error. Thus, management software will think that the live
migration was successful (from just looking at the API response). This
is not expected behaviour.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-29 17:19:28 +00:00
Philipp Schuster
1ae1cc787d vmm: migration seccomp: add for TCP workers (send and receive)
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-29 17:15:14 +00:00
Philipp Schuster
fd88e23ecb vmm: migration seccomp: add for migration worker (coordinator thread)
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-29 17:15:14 +00:00
Philipp Schuster
b5c028c286 vmm: migration seccomp: add plumbing for all migration-related threads
So far, we only have seccomp rules for the postcopy-send thread. This
commit introduces the basic plumbing to add seccomp rules also for the
migration worker (the migration coordinator) as well as the TCP workers
(both, send and receive) in the following.

To streamline code setup, all filters are created at a central place
early in the migration code. Although this means that some filters are
created without the need to do so (e.g., postcopy), this massively
simplifies code setup and error handling. This overhead is negligible.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-29 17:15:14 +00:00
dependabot[bot]
e7371984b8 build(deps): bump the non-rust-vmm group across 2 directories with 12 updates
Bumps the non-rust-vmm group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [itertools](https://github.com/rust-itertools/itertools) | `0.14.0` | `0.15.0` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.28` | `0.2.29` |
| [log](https://github.com/rust-lang/log) | `0.4.32` | `0.4.33` |
| [rustls](https://github.com/rustls/rustls) | `0.23.40` | `0.23.41` |
| [getrandom](https://github.com/rust-random/getrandom) | `0.4.2` | `0.4.3` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.64` | `1.2.65` |
| [quote](https://github.com/dtolnay/quote) | `1.0.45` | `1.0.46` |
| [syn](https://github.com/dtolnay/syn) | `2.0.117` | `2.0.118` |
| [zeroize](https://github.com/RustCrypto/utils) | `1.8.2` | `1.9.0` |

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

| Package | From | To |
| --- | --- | --- |
| [itertools](https://github.com/rust-itertools/itertools) | `0.14.0` | `0.15.0` |
| [log](https://github.com/rust-lang/log) | `0.4.32` | `0.4.33` |
| [rustls](https://github.com/rustls/rustls) | `0.23.40` | `0.23.41` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.64` | `1.2.65` |
| [quote](https://github.com/dtolnay/quote) | `1.0.45` | `1.0.46` |
| [syn](https://github.com/dtolnay/syn) | `2.0.117` | `2.0.118` |



Updates `itertools` from 0.14.0 to 0.15.0
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.14.0...v0.15.0)

Updates `jiff` from 0.2.28 to 0.2.29
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.28...jiff-static-0.2.29)

Updates `log` from 0.4.32 to 0.4.33
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.32...0.4.33)

Updates `rustls` from 0.23.40 to 0.23.41
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.23.40...v/0.23.41)

Updates `getrandom` from 0.4.2 to 0.4.3
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.4.2...v0.4.3)

Updates `cc` from 1.2.64 to 1.2.65
- [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.64...cc-v1.2.65)

Updates `hashbrown` from 0.15.5 to 0.17.0
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.15.5...v0.17.0)

Updates `jiff-static` from 0.2.28 to 0.2.29
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.28...jiff-static-0.2.29)

Updates `quote` from 1.0.45 to 1.0.46
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.45...1.0.46)

Updates `syn` from 2.0.117 to 2.0.118
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.117...2.0.118)

Updates `wit-bindgen` from 0.51.0 to 0.57.1
- [Release notes](https://github.com/bytecodealliance/wit-bindgen/releases)
- [Commits](https://github.com/bytecodealliance/wit-bindgen/compare/v0.51.0...v0.57.1)

Updates `zeroize` from 1.8.2 to 1.9.0
- [Commits](https://github.com/RustCrypto/utils/compare/zeroize-v1.8.2...zeroize-v1.9.0)

Updates `itertools` from 0.14.0 to 0.15.0
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.14.0...v0.15.0)

Updates `log` from 0.4.32 to 0.4.33
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.32...0.4.33)

Updates `rustls` from 0.23.40 to 0.23.41
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.23.40...v/0.23.41)

Updates `cc` from 1.2.64 to 1.2.65
- [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.64...cc-v1.2.65)

Updates `quote` from 1.0.45 to 1.0.46
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.45...1.0.46)

Updates `syn` from 2.0.117 to 2.0.118
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.117...2.0.118)

---
updated-dependencies:
- dependency-name: itertools
  dependency-version: 0.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.29
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: log
  dependency-version: 0.4.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rustls
  dependency-version: 0.23.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: getrandom
  dependency-version: 0.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.65
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: hashbrown
  dependency-version: 0.17.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.29
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: quote
  dependency-version: 1.0.46
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wit-bindgen
  dependency-version: 0.57.1
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zeroize
  dependency-version: 1.9.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: itertools
  dependency-version: 0.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: log
  dependency-version: 0.4.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rustls
  dependency-version: 0.23.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.65
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: quote
  dependency-version: 1.0.46
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-26 16:47:02 +00:00
Philipp Schuster
ebc653d0c8 vmm: silence some info!() messages with little value-add
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-26 16:46:30 +00:00
Philipp Schuster
48c9e9d86b vmm: split code in ReceiveAdditionalConnections into smaller chunks
This ensures the code stays maintainable.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-26 16:46:30 +00:00
Philipp Schuster
6b441f0746 vmm: improve rustdoc for ReceiveAdditionalConnections
ReceiveAdditionalConnections got quite complicated, especially with the
many threads involved for precopy and the special-case of postcopy. We
therefore should add comprehensive documentation.

I tried to keep it short and concise - what remains provides high value
and improves the mental model of the code.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-26 16:46:30 +00:00
Philipp Schuster
4d727c4899 vmm: keep virtio activation alive in migration
Live migration can deadlock if the guest triggers a virtio device
activation while the migration worker owns the VM.

The failure shows up when starting live migrations during boot and
firmware startup, where the guest can reset and reinitialize virtio
devices while precopy is running. In the failing case, the source log
shows a pending virtio activation that never completes:

    8.115833s _virtio-pci-net_0: Needs activation; returning barrier
    8.115854s vmm/src/vm.rs:464 -- Waiting for barrier
    24.875452s Entering downtime phase
    24.875481s stopping vcpu throttling thread
    ...
    vCPU thread did not respond in 10ms to signal - retrying
    vCPU thread did not respond in 20ms to signal - retrying
    ...
    thread 'throttle-vcpu' (1029) panicked
    ...
    Pause(Error signalling vCPUs: Timeout when waiting for signal
        to be acknowledged)

The vCPU blocks on the activation barrier and never reaches the normal
pause checkpoint. Later, migration enters downtime and stops the vCPU
throttle thread. In the failing case, that thread is still inside a
CpuManager::pause() call, which waits for every vCPU to acknowledge
the signal. The blocked vCPU never does, so the pause times out.

Fix this by storing the DeviceManager inside VmOwnership::Migration.
This keeps just enough state on the VMM thread to drain pending virtio
activations while the migration worker owns the Vm. The barrier logic
stays unchanged. The VMM now releases the same activation barrier during
migration that it already released before migration started.

This keeps the guest from getting stuck in the activation wait and
lets the later pause succeed.

Co-authored-by: Leander Kohler <leander.kohler@cyberus-technology.de>
On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-26 16:42:37 +00:00
Sebastian Eydam
08526a65b5 main: print seccomp syscall details
When seccomp traps a SIGSYS, print the syscall number that caused it,
the current thread id and thread name to make violations easier to
debug.

This change requires that all threads are allowed to execute the
`gettid` and the `prctl` syscalls, thus the seccomp filters have also
been adjusted.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-26 16:40:45 +00:00
Sebastien Boeuf
693c236e06 vm-migration: Use zerocopy for safe serialization
Getting rid of the unsafe ByteValued implementation for MemoryRange,
Request and Response structures, by relying on zerocopy's safe
implementation instead.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-8
2026-06-26 14:59:45 +00:00
Philipp Schuster
516caed5cc vmm: streamline rustdoc of tls_dir
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-26 01:12:59 +00:00
Philipp Schuster
9dc970630b vmm: log successful TCP/TLS handshake
This helps operators and developers to easily verify if the TLS
handshake was successful.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-26 01:12:59 +00:00
Philipp Schuster
90429c56c8 vmm: streamline error messages
In [0] we agreed on the current format.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7066

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-25 19:13:13 +00:00
Philipp Schuster
40d8f9ee51 pci: streamline error messages
In [0] we agreed on the current format.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7066

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-25 19:13:13 +00:00
Philipp Schuster
fdc076d22f net_util: streamline error messages
In [0] we agreed on the current format.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7066

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-25 19:13:13 +00:00
Philipp Schuster
786d40f549 block: streamline error messages
In [0] we agreed on the current format.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7066

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-25 19:13:13 +00:00
Philipp Schuster
9ba653dc16 misc: persist Error-message style in CONTRIBUTING.md and AGENTS.md
In [0] we agreed on the current format.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7066

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-25 19:13:13 +00:00
Philipp Schuster
252702049e vmm: migration: print whole error chain on error
In [0] we agreed on the current format. When ch-remote or
cloud-hypervisor exit with an error, they nicely print the whole chain.
This, however, doesn't work when simply doing `error!("error: {e}")`
- which is what we currently do for migration-related errors.

This commit walks the chain of errors and prints all components in a
single line. This massively improves the quality of error messages and
helps tracing down where an error is originating from. Using ` => ` as
separator is better than `\n` which doesn't work well in our current
log format.

# Example (Before - Bad)

```
cloud-hypervisor:   2.859287s: <vmm> ERROR:vmm/src/lib.rs:2021 -- Migration failed: Failed to send migratable component snapshot
```

# Example (New - Better)

```
cloud-hypervisor:   2.296160s: <vmm> ERROR:vmm/src/lib.rs:2038 -- Migration failed: Failed to send migratable component snapshot => Error connecting to TCP socket => Connection refused (os error 111)
```

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7066

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-25 19:12:15 +00:00
Philipp Schuster
8c71a0d821 vmm: make migration errors more helpful
Keep the original error sources while adding operation context to the
straightforward migration send and receive paths. This keeps up a
chain of errors that can be printed nicely and in a helpful way.

This commit does that for all MigratableError-occurences where this
change is easily applicable.

Co-authored-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-25 19:12:15 +00:00
Henry Hrvoje Tonkovac
05f026440a devices: rtc_pl031: update outdated ARM TRM doc link
static.docs.arm.com no longer serves the PL031 RTC technical reference
manual; point the comment at the current developer.arm.com location.
Clears the lychee link check, as requested on the review.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-25 16:54:35 +00:00
Henry Hrvoje Tonkovac
f720e619c1 misc: use prelude size_of
size_of is part of std::prelude as of Rust 1.80 (with size_of_val,
align_of, align_of_val), and the workspace MSRV is 1.89, so qualifying
it (mem::size_of, std::mem::size_of, core::mem::size_of) is unnecessary.

Convert every qualified size_of call-site to the bare prelude form and
drop the now-redundant `use std::mem::size_of;` imports, keeping
`use std::mem;` where it still serves non-prelude items (transmute,
swap, replace, take, zeroed, MaybeUninit, offset_of). size_of is the
only one of the four currently used in the tree.

Pure refactor, no behavioural change. Follow-up to the
clippy::absolute_paths cleanup (#7670), as discussed in #8444.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-25 16:54:35 +00:00
Henry Hrvoje Tonkovac
f56fa3a865 tests: trim qualified paths in integration
Import the modules used in the integration tests instead of spelling
the full paths at every use site, and drop the file's now-unnecessary

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-24 15:13:07 +00:00
Henry Hrvoje Tonkovac
025e782e50 vmm: trim qualified paths
Import the modules used in the crate instead of spelling the full paths
at every use site, and drop the now-unnecessary crate-level

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-24 15:12:32 +00:00
Philipp Schuster
0caa3ee73f vmm: silence some info! messages
We have larger cloud deployments and analyzed the logs. Let's silence
some messages that generally provide little value on the `info!` level.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-24 13:58:46 +00:00
Philipp Schuster
dcaccf21ea virtio-devices: silence some info! messages
We have larger cloud deployments and analyzed the logs. Let's silence
some messages that generally provide little value on the `info!` level.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-24 13:58:46 +00:00
Sebastien Boeuf
cc98a232e6 vmm: Add seccomp filter for migrate-send-postcopy thread
Applying seccomp filtering to the migration postcopy thread running on
the source VM during migration.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-8
2026-06-24 12:51:40 +00:00
Sebastien Boeuf
80958acdab vmm: Wire postcopy live migration from source VM
Wire up the source side of postcopy migration over TCP. When
`mode=postcopy` is requested on vm.send-migration, the source skips
the pre-copy dirty-tracking loop and lets the destination resume early,
then serves guest pages on demand over a dedicated connection.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-24 12:51:40 +00:00
Sebastien Boeuf
60398f11ff offload_daemon: Add --ondemand restore mode
Add an --ondemand flag to the offload daemon's restore subcommand to
support the post-copy mechanism from the live migration protocol.

In on-demand mode, the daemon creates empty memfds to back the guest
memory and sends them over to the VMM. This lets the VM start quickly,
right after the memfds are mapped into CH's address space.

At runtime, when the guest accesses a page (or the prefault handler
requests it), the daemon faults it in by copying the page content into
its shared memory mapping, then replies to the PageFault request so the
VMM can consider the page present.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-24 12:51:40 +00:00
Sebastien Boeuf
48ba1f1417 vmm: Add postcopy support to receive-migration
Plumb the SocketUffdMemorySource into the receiving side of live
migration. When memory_mode=postcopy is requested, the destination
brings up a dedicated fault connection, registers userfaultfd on the
restored memory regions, and serves guest pages on demand over that
connection while the VM resumes early.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-24 12:51:40 +00:00
Sebastien Boeuf
282d1c989d vmm: Add SocketUffdMemorySource implementation
Add the socket-backed UffdMemorySource that resolves each fault by
sending a Command::PageFault request to the peer over a dedicated fault
connection.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-24 12:51:40 +00:00
Sebastien Boeuf
0d82d16432 vmm: Introduce MigrationMode migration option
Introducing a migration mode to both sides of the migration (send and
receive), so that a user can desribe which way the memory should be
migrated between the source and destination VMs.

For now, we only introduce `precopy` and `postcopy` as viable options,
but we can expect other modes (more optimized) to be added in the
future.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-8
2026-06-24 12:51:40 +00:00
Sebastien Boeuf
3807dc82de vmm: Refactor restore_by_uffd behind UffdMemorySource trait
Extract the page content provider out of the userfaultfd handler so it
can be plugged with different backends in followup commits.

No functional change intended.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-24 12:51:40 +00:00
Sebastien Boeuf
a887d87c2a vm-migration: Add PageFault command for postcopy and on demand restore
Introducing PageFault as the new wire command needed by both postcopy
live migration and on demand restore from the offload daemon. This new
command describes the need from the destination to fault the page
content in. This request describes the page through a MemoryRange
structure, and the response can be either 0 or the actual page size.

In case it is 0, that means the source had access to the guest memory
and was able to copy the page content directly. In case the response is
the actual page size, there is a payload associated which contains the
page content.

We can expect local live migration and offload restore to run locally
and therefore have access to the guest memory. The remote live migration
over the network is the case where we would expect the page content to
be sent over the wire.

This command is served through an additional connection happening on the
UNIX or TCP socket. The goal is to keep the same codepath between local
and remote migrations. This additional channel allows PageFault commands
to be issued asynchronously so they can be served without blocking the
main connection.

A connection role is introduced in order to identify an additional
connection related to pre-copy memory versus the newly introduced
channel for serving post-copy requests.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-24 12:51:40 +00:00
Anatol Belski
755d42eec0 block: Remove redundant BlockBackend trait
BlockBackend predated the disk_file trait family and only carried
logical_size and physical_size, which the disk backends expose
through the disk_file traits DiskSize and PhysicalSize.

It added no polymorphism while its Read, Write and Seek supertraits
forced an unused cursor. Dropping the trait removes the dead code
it was keeping alive.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-24 01:26:12 +00:00
Henry Hrvoje Tonkovac
17cc156ccb tests: trim qualified paths in integration_cvm
Import the modules used in the shared common test helpers instead of
spelling the full paths at every use site, and drop the now-unnecessary
crate-level #![expect(clippy::absolute_paths)] from integration_cvm.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-23 18:15:40 +00:00
Henry Hrvoje Tonkovac
01de980615 hypervisor: trim qualified paths
Import the modules used in the crate instead of spelling the full paths
at every use site, and drop the now-unnecessary crate-level
generated msr_index.rs was trimmed separately.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-23 16:19:10 +00:00
Henry Hrvoje Tonkovac
bb81c6650b ch-remote: trim qualified paths
Import the modules used in the binary instead of spelling the full
paths at every use site, and drop the now-unnecessary crate-level
#![expect(clippy::absolute_paths)].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-23 00:58:11 +00:00
Rob Bradford
993ac5c90b vmm: cpu: Re-sync vCPU TSC offsets after restore
Restoring a snapshot (snapshot restore or live-migration receive) sets
each vCPU's TSC by writing MSR_IA32_TSC as the vCPU is created.

However because CpuManager creates and restores vCPUs one at a time, the
host TSC advances between the per-vCPU writes and KVM derives a slightly
different TSC offset for each vCPU.

KVM only engages its masterclock when every offset matches. This has a
side effect of breaking the HyperV TSC reference clock page resulting in
significantly reduced performance on Windows.

After restore synchronise all vCPU's TSC offset to the boot vCPU's via
the KVM_VCPU_TSC_CTRL device attribute group (Linux 5.16+) this allows
the KVM TSC masterclock to engage and mitigates performance issues with
the KVM HyperV emulation.

See: #8383

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude <claude-opus-4-8>
2026-06-22 20:13:22 +00:00
Rob Bradford
08c82a7352 hypervisor: kvm: Reduce fully qualified paths
Reduce, but don't fully eliminate the fully qualified std:: paths.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-22 20:13:22 +00:00
Henry Hrvoje Tonkovac
f905a4e9d2 main: trim qualified paths
Import the modules used in the binary instead of spelling the full
paths at every use site, and drop the now-unnecessary crate-level
#![expect(clippy::absolute_paths)].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-22 19:14:50 +00:00
Philipp Schuster
6a16b65ea6 tests: adjust to new dispatch semantics of ch-remote send-migration
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-22 19:11:42 +00:00
Philipp Schuster
a56594324c vmm: move migration modules into folder
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-22 19:11:42 +00:00
Philipp Schuster
7c7a827ded vmm: streamline request handlers to use match{} on vm
This streamlines the behavior with the other request handlers so that
now almost every request handler uses a match on self.vm.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-22 19:11:42 +00:00
Philipp Schuster
796fc055bd vmm: migration: handle in dedicated thread (make async)
This puts the send-migration action into a dedicated thread, laying the
groundwork for many follow-ups towards first-class live-migration in
CH.

This means:

1. The send-migration call will exit sooner (just trigger the
   migration - dispatch semantics)
2. Other API calls can be triggered while a migration is ongoing but
   will not be able to alter the VM as the VM's ownership is transferred
   from the VMM to the migration thread. Example: hotplugging won't work
   (which is good).
3. This is the basis for migration statistics via a dedicated endpoint
   (future work).

The whole change was done with a special focus on graceful recover and
cleanup: even if anything on the migration paths go wrong, the proper
cleanups are already executed and the VMM can take back the ownership
of the VM.

The receive-migration API call remains blocking. To observe any status
changes about the migration on the sender side, one can observe the
event-monitor output and look for `vm.migration-{failed,finished}`.

These changes are inspired by [0] but differ significantly in details.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7038

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-22 19:11:42 +00:00
Philipp Schuster
5d835bdff4 vmm: migration: prepare EventFd for async migration events
This is a pre-requisite for the following commit which puts the
migration into a dedicated thread. It allows the VMM to react to
migration events (success/failure).

The commit series was inspired by @ljcore [0] but was changed quite
significantly.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7038

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-22 19:11:42 +00:00
Philipp Schuster
e034567690 vmm: init migration worker module
This initializes the module and the thread that will handle (control)
the migration. This introduces the new types without the necessary
wiring.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-22 19:11:42 +00:00
Henry Hrvoje Tonkovac
a8d7441c59 main: trim qualified paths in lib
Trim the one fully-qualified std::iter::successors path in the library
crate down to an imported module, and drop the now-unnecessary
crate-level #![expect(clippy::absolute_paths)].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-22 14:20:46 +00:00
Anatol Belski
1db8858fac virtio-devices: block: Reuse descriptor chain's memory for queue enable
The two synchronous completion paths add the head to the used ring with
desc_chain.memory() but reload self.mem.memory() to enable
notifications. Keep both on the snapshot the chain was parsed from so
the used ring update and the notification enable always act on one
guest memory view rather than two independent atomic loads.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-21 19:46:46 +00:00
Anatol Belski
dd2f18e73e tests: Cover direct IO data disks on 4k sector FS
Add a parameterized helper that creates a 1 GiB ext4 loop filesystem
with 4096 byte sectors, populates it with a small data disk in the
requested format, attaches that disk with direct=on, and runs a 4096
byte aligned dd round trip with oflag=direct and iflag=direct
followed by cmp.

Wrappers exercise raw, qcow2, fixed VHD, and vhdx. The qcow2 and vhdx
wrappers expect the guest to see the on disk LBS of 512. The others
expect the host LBS of 4096.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-20 11:46:17 +00:00
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
Anatol Belski
80cc980d05 performance-metrics: Detect boot via the cloud-init callback
The boot time tests inferred guest readiness from the two debug I/O
port markers on stderr. Heavier boots could miss the fixed sleep
window, capture a single marker, and panic.

Wait on the cloud-init injected notify-booted callback through
guest.wait_vm_boot() instead. Once the callback fires, both markers
are guaranteed present, so they are parsed only for the metric. The
host side keeps an overall timeout, so a guest that never boots is
still reaped.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-20 08:29:23 +00:00
dependabot[bot]
b0610f3e91 build(deps): bump the non-rust-vmm group across 2 directories with 18 updates
Bumps the non-rust-vmm group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.2` | `1.23.3` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.50` | `0.8.52` |
| [smallvec](https://github.com/servo/rust-smallvec) | `1.15.1` | `1.15.2` |
| [block-buffer](https://github.com/RustCrypto/utils) | `0.12.0` | `0.12.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.63` | `1.2.64` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.100` | `0.3.102` |
| [memchr](https://github.com/BurntSushi/memchr) | `2.8.1` | `2.8.2` |
| [openssl-src](https://github.com/alexcrichton/openssl-src-rs) | `300.6.0+3.6.2` | `300.6.1+3.6.3` |
| [openssl-sys](https://github.com/rust-openssl/rust-openssl) | `0.9.116` | `0.9.117` |
| [regex](https://github.com/rust-lang/regex) | `1.12.3` | `1.12.4` |
| [rustls-pki-types](https://github.com/rustls/pki-types) | `1.14.0` | `1.14.1` |
| [wasip2](https://github.com/bytecodealliance/wasi-rs) | `1.0.3+wasi-0.2.9` | `1.0.4+wasi-0.2.12` |

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

| Package | From | To |
| --- | --- | --- |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.2` | `1.23.3` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.50` | `0.8.52` |
| [smallvec](https://github.com/servo/rust-smallvec) | `1.15.1` | `1.15.2` |
| [block-buffer](https://github.com/RustCrypto/utils) | `0.12.0` | `0.12.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.63` | `1.2.64` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.100` | `0.3.102` |
| [memchr](https://github.com/BurntSushi/memchr) | `2.8.1` | `2.8.2` |
| [wasip2](https://github.com/bytecodealliance/wasi-rs) | `1.0.3+wasi-0.2.9` | `1.0.4+wasi-0.2.12` |



Updates `uuid` from 1.23.2 to 1.23.3
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.2...v1.23.3)

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

Updates `smallvec` from 1.15.1 to 1.15.2
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.15.1...v1.15.2)

Updates `block-buffer` from 0.12.0 to 0.12.1
- [Commits](https://github.com/RustCrypto/utils/compare/block-buffer-v0.12.0...block-buffer-v0.12.1)

Updates `cc` from 1.2.63 to 1.2.64
- [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.63...cc-v1.2.64)

Updates `js-sys` from 0.3.100 to 0.3.102
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `memchr` from 2.8.1 to 2.8.2
- [Commits](https://github.com/BurntSushi/memchr/compare/2.8.1...2.8.2)

Updates `openssl-src` from 300.6.0+3.6.2 to 300.6.1+3.6.3
- [Release notes](https://github.com/alexcrichton/openssl-src-rs/releases)
- [Commits](https://github.com/alexcrichton/openssl-src-rs/commits)

Updates `openssl-sys` from 0.9.116 to 0.9.117
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.116...openssl-sys-v0.9.117)

Updates `regex` from 1.12.3 to 1.12.4
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.3...1.12.4)

Updates `regex-syntax` from 0.8.10 to 0.8.11
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-syntax-0.8.10...regex-syntax-0.8.11)

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

Updates `wasip2` from 1.0.3+wasi-0.2.9 to 1.0.4+wasi-0.2.12
- [Commits](https://github.com/bytecodealliance/wasi-rs/compare/wasip2-1.0.3...wasip2-1.0.4)

Updates `wasm-bindgen` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-macro` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-macro-support` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-shared` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

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

Updates `uuid` from 1.23.2 to 1.23.3
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.2...v1.23.3)

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

Updates `block-buffer` from 0.12.0 to 0.12.1
- [Commits](https://github.com/RustCrypto/utils/compare/block-buffer-v0.12.0...block-buffer-v0.12.1)

Updates `cc` from 1.2.63 to 1.2.64
- [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.63...cc-v1.2.64)

Updates `js-sys` from 0.3.100 to 0.3.102
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `memchr` from 2.8.1 to 2.8.2
- [Commits](https://github.com/BurntSushi/memchr/compare/2.8.1...2.8.2)

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

Updates `smallvec` from 1.15.1 to 1.15.2
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.15.1...v1.15.2)

Updates `wasip2` from 1.0.3+wasi-0.2.9 to 1.0.4+wasi-0.2.12
- [Commits](https://github.com/bytecodealliance/wasi-rs/compare/wasip2-1.0.3...wasip2-1.0.4)

Updates `wasm-bindgen` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-macro` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-macro-support` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-shared` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

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

Updates `uuid` from 1.23.2 to 1.23.3
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.2...v1.23.3)

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

Updates `smallvec` from 1.15.1 to 1.15.2
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.15.1...v1.15.2)

Updates `block-buffer` from 0.12.0 to 0.12.1
- [Commits](https://github.com/RustCrypto/utils/compare/block-buffer-v0.12.0...block-buffer-v0.12.1)

Updates `cc` from 1.2.63 to 1.2.64
- [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.63...cc-v1.2.64)

Updates `js-sys` from 0.3.100 to 0.3.102
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `memchr` from 2.8.1 to 2.8.2
- [Commits](https://github.com/BurntSushi/memchr/compare/2.8.1...2.8.2)

Updates `wasip2` from 1.0.3+wasi-0.2.9 to 1.0.4+wasi-0.2.12
- [Commits](https://github.com/bytecodealliance/wasi-rs/compare/wasip2-1.0.3...wasip2-1.0.4)

Updates `wasm-bindgen` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-macro` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-macro-support` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-shared` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

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

Updates `uuid` from 1.23.2 to 1.23.3
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.2...v1.23.3)

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

Updates `block-buffer` from 0.12.0 to 0.12.1
- [Commits](https://github.com/RustCrypto/utils/compare/block-buffer-v0.12.0...block-buffer-v0.12.1)

Updates `cc` from 1.2.63 to 1.2.64
- [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.63...cc-v1.2.64)

Updates `js-sys` from 0.3.100 to 0.3.102
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `memchr` from 2.8.1 to 2.8.2
- [Commits](https://github.com/BurntSushi/memchr/compare/2.8.1...2.8.2)

Updates `smallvec` from 1.15.1 to 1.15.2
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.15.1...v1.15.2)

Updates `wasip2` from 1.0.3+wasi-0.2.9 to 1.0.4+wasi-0.2.12
- [Commits](https://github.com/bytecodealliance/wasi-rs/compare/wasip2-1.0.3...wasip2-1.0.4)

Updates `wasm-bindgen` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-macro` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-macro-support` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

Updates `wasm-bindgen-shared` from 0.2.123 to 0.2.125
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.123...0.2.125)

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

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.52
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: smallvec
  dependency-version: 1.15.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: block-buffer
  dependency-version: 0.12.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.64
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.102
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: memchr
  dependency-version: 2.8.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: openssl-src
  dependency-version: 300.6.1+3.6.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: openssl-sys
  dependency-version: 0.9.117
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: regex
  dependency-version: 1.12.4
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: regex-syntax
  dependency-version: 0.8.11
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rustls-pki-types
  dependency-version: 1.14.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasip2
  dependency-version: 1.0.4+wasi-0.2.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.52
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.52
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: block-buffer
  dependency-version: 0.12.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.64
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.102
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: memchr
  dependency-version: 2.8.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rustls-pki-types
  dependency-version: 1.14.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: smallvec
  dependency-version: 1.15.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasip2
  dependency-version: 1.0.4+wasi-0.2.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.52
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.52
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: smallvec
  dependency-version: 1.15.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: block-buffer
  dependency-version: 0.12.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.64
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.102
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: memchr
  dependency-version: 2.8.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasip2
  dependency-version: 1.0.4+wasi-0.2.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.52
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.52
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: block-buffer
  dependency-version: 0.12.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.64
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.102
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: memchr
  dependency-version: 2.8.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: smallvec
  dependency-version: 1.15.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasip2
  dependency-version: 1.0.4+wasi-0.2.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.125
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.52
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 09:46:02 +00:00
Rob Bradford
6fee484715 vmm: Reject resizing below the boot size with an error
This was already handled with user memory zones but not with the default
memory. Make a small refactoring to move the boot RAM check into
MemoryManager rather than split across Vm and MemoryManager.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-19 08:11:16 +00:00
Wei Liu
dbb33a5645 pci: leak the address when munmap fails
This is more lenient than aborting the whole process. Leaking memory is
safe in Rust.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-19 00:53:58 +00:00
dependabot[bot]
9d743b012b build(deps): bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 00:40:04 +00:00
Henry Hrvoje Tonkovac
066091a54c arch: trim qualified paths
Import the std modules used in the crate instead of spelling the full
paths at every use site, and drop the now-unnecessary crate-level
#![expect(clippy::absolute_paths)].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-18 23:08:10 +00:00
Atish Patra
eb1c64e4f0 tests: integration: assert same-host pause/resume keeps aarch64 clock
Add an aarch64 test that pauses a running VM, waits out an interval, and
resumes it on the same host, then asserts the guest wall clock still
matches the host. On aarch64 the architected counter free-runs across
the pause, so the guest self-corrects.

The downtime and skew tolerance are shared with the snapshot clock test.
x86_64 has its own kvmclock path and is covered by the snapshot clock
test.

Signed-off-by: Atish Patra <atishp@meta.com>
2026-06-18 22:59:37 +00:00
Atish Patra
eb2dc28edc tests: integration: assert the guest clock catches up across restore
Add a variation of _test_snapshot_restore that, after taking a snapshot,
waits out a simulated off-host interval and then restores and resumes,
asserting that the guest's wall clock has caught up to the host. This
exercises the clock catch-up that each architecture provides on restore:
kvmclock (KVM_CLOCK_REALTIME) on x86_64 today, and the CNTVCT advance on
aarch64 with later commits.

On x86_64 the guest is booted with clocksource=kvm-clock as the guest
clock is caught up after pause/resume only in that mode. A
tsc-clocksource guest's restored TSC freezes across the interval and
would never catch up.

Take this opportunity to improve the snapshot restore test as the
existing bare boolean mechanism was bit hard to read with new test.

Signed-off-by: Atish Patra <atishp@meta.com>
2026-06-18 22:59:37 +00:00
Atish Patra
25271c9d0c hypervisor: aarch64: advance the guest counter on restore and migration
Currently, Cloud Hypervisor round-trips CNTVCT_EL0 through
KVM_GET_REG_LIST/SET_ONE_REG, which leaves a cold-restored or migrated
guest behind real UTC by the downtime. Same-host pause/resume
self-corrects (the physical counter keeps running across the pause), so
only restore and migration cases required the clock to catch up to wall
clock time.

Since ARM has no kernel helper, compute the difference in wall clock
time and compute the ticks so that it can advance the CNTVCT correctly.
It is set via vcpu0 only as it affects a single VM wide value after
Linux 6.4. For older kernels, it was a truly vcpu value which needs to
be invoked for every vcpu.

Gated on all(target_arch = "aarch64", feature = "kvm"); x86 is
unchanged.

Basic manual test case (aarch64 + KVM) verified both in intra host and
inter host snapshot save/restore:

1. Boot a Linux guest; in the guest, `date -u` tracks the host's UTC.
2. Pause and snapshot the VM (ch-remote pause; ch-remote snapshot
   file:///<dir>).
3. Leave it down for several minutes (the off-host interval).
4. Restore and resume into a fresh VMM (ch-remote restore
   source_url=file:///<dir>,resume=true).
5. In the guest, run `date -u` again and compare to the host: the guest
   now tracks current UTC, having advanced by ~the time it spent down.

Before this change the restored guest reads behind real UTC by the
downtime; after it, the guest clock is back in sync (to within the
snapshot-to-restore sampling slop).

Signed-off-by: Atish Patra <atishp@meta.com>
2026-06-18 22:59:37 +00:00
Atish Patra
69637dde69 hypervisor: aarch64: capture the guest counter for snapshot/restore
Unlike x86, ARM64 has no kvmclock support to sync guest time upon
required. However, the guest reads the architected virtual timer
(CNTVCT_EL0) directly which can be modified by the VMM to update the
time after snapshot restore. Since the CNTVCT is in ticks, we also need
to read CNTFRQ (via mrs due to lack of ONEREG interface) to compute the
ticks from wall clock difference.

Because the counter is a vCPU register, the capture must run with the
vCPUs quiesced, so the VMM now captures the clock just after
cpu_manager.pause() through the boot vCPU. This is behaviorally
identical for x86, whose clock is VM-wide. There is no restore/advance
yet, so aarch64 guests still resume behind real time until the following
commit.

Signed-off-by: Atish Patra <atishp@meta.com>
2026-06-18 22:59:37 +00:00
Atish Patra
ad909a3d71 vmm: drive guest clock save/restore through the vm abstraction
Currently, VM pause/resume/snapshot paths invoke architecture specific
bits for guest clock udpates which ideally belongs to hypervisor layer.

Route it through the snapshot_clock()/restore_clock() pair added in the
previous commit instead, so the VMM no longer depends on an architecture
specific clock API and the upcoming aarch64 backend can hook the same
path without a parallel branch in vm.rs.

Signed-off-by: Atish Patra <atishp@meta.com>
2026-06-18 22:59:37 +00:00
Atish Patra
d68b93ea93 hypervisor: add a generic guest-clock save/restore abstraction
Preserving the guest clock across pause/resume and snapshot/restore is
currently open-coded in the VMM against the x86-only
get_clock/set_clock. aarch64 needs the same correction but via a
different mechanism (i.e. the architected counter, CNTVCT). Having a
common backend-agnostic interface that VMM can drive uniformly allows us
to keep the architecture details behind the Hypervisor abstraction.

This commit only introduces the abstraction while the future commits
will actually move the implementation to use it.

Use this opportunity to fix the full path to get SystemTime as well.

Suggested-by: Sebastien Boeuf <sboeuf@meta.com>
Signed-off-by: Atish Patra <atishp@meta.com>
2026-06-18 22:59:37 +00:00
Henry Hrvoje Tonkovac
00edf5b34d hypervisor: trim qualified paths in msr_index
Import the std module used in the generated MSR-index file instead of
spelling the full paths at every use site.

The crate-level #![expect(clippy::absolute_paths)] is kept; the
remaining sites in the rest of the crate are handled by a follow-up.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-18 21:17:14 +00:00
Thomas Prescher
490a9a87af vmm: Emit complete ACPI S5 sleep package
OpenBSD expects the ACPI _S5_ object to provide both sleep type values.
The single-value package made acpi_init_states() parse an invalid object
and fault during early ACPI setup with:

```
...
acpi0 at bios0: ACPI 6.3
acpi0: sleep statesfatal protection fault in supervisor mode
trap type 4 code 0 rip ffffffff814af264 cs 8 rflags 10282 cr2 0 cpl e rsp ffffffff81a06a30
gsbase 0xffffffff81755ff0  kgsbase 0x0
panic: trap type 4, code=0, pc=ffffffff814af264
Starting stack trace...
panic(ffffffff81a06980,4,ffffffff81a06a58,ffffffff81756ae0,ffffffff81a06960,ffffffff81a068e0) at panic+0x12e
kerntrap(ffffffff81a06aa0,ffff800000232400,ffffffff81261681,ffffffff81a06970,ffffffff81a06980,4) at kerntrap+0xe1
alltraps_kern_meltdown(4,ef0d316e102be1f4,ffff800000232480,0,ffffffff81a06aa0,ffff800000232400) at alltraps_kern_meltdown+0x7b
aml_val2int(ef0d316e102be1f4,0,10,ffffffff81a06a30,10282,8) at aml_val2int+0x24
acpi_init_states(1,ffff800000232400,ffff800000232470,0,ef0d316e102be1f4,5f35535f) at acpi_init_states+0xd5
acpi_attach_common(ffff800000235300,ffffffff81a06cf0,ffffffff81762a00,ffff800000232400,ffff800000232424,ef0d316e102be1f4) at acpi_attach_common+0x311
config_attach(ffffffff81a06d30,ffff800000235300,50,118,ffff80003158c004,ffffffff813e3270) at config_attach+0x1d2
bios_attach(ffff800000235280,ffffffff81a06e28,ffffffff8175ca50,ffff800000235300,ffff800000235324,ef0d316e102be1f4) at bios_attach+0x898
config_attach(ffffffff81a06e28,ffff800000235280,ffffffff81757e68,ffff800000235280,ffff8000002352a4,ffffffff8128c510) at config_attach+0x1d2
mainbus_attach(0,0,ef0d316e102be1f4,ffffffff81a06e50,ffffffff81a06ec0,3000000010) at mainbus_attach+0x70
config_attach(8,1001000,805f50,1000000,ffffffff81a00008,0) at config_attach+0x1d2
cpu_configure(8,1001000,ffffffff814f3859,ffffffff81a06f20,8,1001000) at cpu_configure+0x29
main(1001000,ef0d316e102be1f4,ffffffff812e8b2f,ffffffff81a06f40,8,1001000) at main+0x3af
end trace frame: 0x0, count: 244
End of stack trace.
```

Advertise S5 as the conventional four-element package as described in
the ACPI spec [1]. Cover the generated AML bytes with a unit test.

In AML, the package now looks like this:

```
Name (_S5, Package () {
  0x05, 0x05, 0x00, 0x00
})
```

[1] https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/07_Power_and_Performance_Mgmt/oem-supplied-system-level-control-methods.html#sx-system-states

On-behalf-of: SAP thomas.prescher@sap.com
Signed-off-by: Thomas Prescher <thomas.prescher@cyberus-technology.de>
2026-06-18 18:38:01 +00:00
Rob Bradford
24aeb1ed71 build: Don't use aws-lc-rs for TLS
In #8053 it was agreed to use the ring backend in preference to
aws-lc-rs as it is pure Rust. However since aws-lc-rs is a default
feature of rustls the addition of ring was additive not a replacement.
Fix the features to exclude awc-lc-rs.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-18 18:27:33 +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
Rob Bradford
1699a81f21 build: Disable rate-limiter job on MQ
This is not a blocking job but the worker is no longer handling the jobs
so it remains pending indefinitely.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-18 16:56:32 +00:00
Henry Hrvoje Tonkovac
74a749b960 virtio-devices: trim qualified paths
Import the modules used in the crate instead of spelling the full paths
at every use site, and drop the now-unnecessary crate-level
#![expect(clippy::absolute_paths)].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-18 15:55:15 +00:00
Sebastien Boeuf
2f2f709a0e docs: Document offload snapshot/restore
Extending the snapshot/restore documentation so that it explains what
are the goals behind this offloaded snapshot/restore feature, how to use
it in practice, and also by documenting the protocol used by the offload
daemon so that anyone could write its own daemon.

By relying on the existing local live migration support and reusing the
semantics and the protocol associated with it, we intend to provide a
way for snapshotting and restoring a VM to/from a dedicated process that
we can call the offload daemon.

By allowing an external process to perform the snapshot/restore actions
on behalf of Cloud Hypervisor, we give our users the opportunity to
implement their own offloaded daemon. The goal is to avoid bloating
Cloud Hypervisor with numerous features related to snapshot/restore, and
let the user decide how to perform the snapshot/restore actions. One
example is that we can decide to encrypt the guest RAM on the fly in
order to avoid writing an unencrypted version to local disk. Another
example is to be able to send guest RAM and associated state/config data
over the network without having to persist the data first to local
storage.

There might be other reasons to choose going with an offloaded daemon to
perform the snapshot/restore of the VM, but in every case, this empowers
the user to make their own choice.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-18 13:45:36 +00:00
Sebastien Boeuf
28b6b5d467 offload_daemon: Implement sparse snapshot/restore
Copy only populated extents when writing the snapshot file and when
filling the restore memfd, leaving unwritten ranges as holes. Both
the on-disk snapshot and the restored guest RAM stay sparse, so that
untouched guest pages cost no disk space or host memory.

This brings the offload daemon closer to be at feature parity with CH's
internal implementation of snapshot/restore. The only missing piece is
on-demand paging at this point.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-18 13:45:36 +00:00
Sebastien Boeuf
df5d2d6003 vmm: Extract sparse file-copy helpers into a reusable module
Move next_data_extent and write_region_sparse out of memory_manager.rs
into a new vmm::sparse module so the snapshot writer, the restore
reader, and the offload daemon can share one implementation.

No functional change intended.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-18 13:45:36 +00:00
Sebastien Boeuf
6a74021ad5 ci: Add integration test for offload snapshot
Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-18 13:45:36 +00:00
Sebastien Boeuf
4992fabd15 offload_daemon: Introduce a reference implementation
Adding a new dedicated binary that is meant to be used as a reference
implementation for validating that offloaded snapshot/restore works and
meant to be used through tests in general.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-18 13:45:36 +00:00
Sebastien Boeuf
cca8e1cd52 vmm: Export VmMigrationConfig as public
Expose VmMigrationConfig as a public facing structure that can be used
by an offload daemon to act as if it was the VM to migrate to, or the VM
to migrate from.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-06-18 13:45:36 +00:00
Henry Hrvoje Tonkovac
d7c86b8b67 devices: trim qualified paths
Import the std modules used in the crate instead of spelling the full
paths at every use site, and drop the now-unnecessary
#[cfg_attr(target_arch = "x86_64", expect(clippy::absolute_paths))].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-18 11:33:58 +00:00
Henry Hrvoje Tonkovac
4b06dacc0b test_infra: trim qualified paths
Import the std modules used in the crate instead of spelling the full
paths at every use site, and drop the now-unnecessary crate-level
#![expect(clippy::absolute_paths)].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-18 11:16:12 +00:00
yanjianqing
58c08ffcca vmm: seccomp: Add SYS_fsync to vcpu thread
Fix disk hot unplug failure caused by seccomp SIGSYS kill.

When performing disk hot unplug, the vcpu thread calls fsync()
on the block device file descriptor to flush pending I/O.
The seccomp filter previously blocked SYS_fsync, triggering SIGSYS
and terminating the vcpu thread, which makes the hot unplug
operation fail. This issue exists on both x86 and AArch64.

Strace log snippet captured during failure:
```
[pid 3118852] fsync(142) = 142
[pid 3118852] ---SIGSYS {si_signo=SIGSYS,si_code=SYS_SECCOMP,si_call_addr=0xffff9c931df8, si_syscall=__NR_fsync,si_arch=AUDIT_ARCH_AARCH64}
```
Add unrestricted SYS_fsync entry to vcpu thread syscall allowlist,
consistent with existing file I/O syscalls such as fcntl and fstat.

Signed-off-by: yanjianqing <yanjianqing@kylinos.cn>
2026-06-18 08:52:39 +00:00
Rob Bradford
61193de6e3 pci: msix: Reject mis-sized MSI-X table and PBA reads
Replace assertions for incorrect access sizes with logged errors. The
write_table() method already handled it like this and this commit
extends the same pattern to read_table() and read_pba().

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-17 19:48:43 +00:00
Henry Hrvoje Tonkovac
427c4de928 performance-metrics: trim qualified paths
Import the std modules used in the crate instead of spelling the full
paths at every use site, and drop the now-unnecessary crate-level
#![expect(clippy::absolute_paths)].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-17 17:28:07 +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
ff83b27937 vhost_user_net: reevaluate #[allow] attributes
Convert the still-needed #[allow] to #[expect] so it warns if the
lint stops firing.

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-17 17:14:00 +00:00
Tushar Khatri
5467db8c54 tracer: reevaluate #[allow] attributes
Convert the still-needed #[allow] to #[expect] so it warns if the
lint stops firing.

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-17 17:14:00 +00:00
Tushar Khatri
a87b73a585 test_infra: 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
Tushar Khatri
bf68b35ab7 performance-metrics: reevaluate #[allow] attributes
Convert the still-needed #[allow] to #[expect] so it warns if the
lint stops firing.

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-17 17:14:00 +00:00
Tushar Khatri
510aa438f8 tests: 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
Tushar Khatri
1555b59d42 ch-remote: reevaluate #[allow] attributes
Convert the still-needed #[allow] to #[expect] so it warns if the
lint stops firing.

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-17 17:14:00 +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
Henry Hrvoje Tonkovac
9c0bceceb1 tpm: trim qualified paths
Import the std modules used in the crate instead of spelling the full
paths at every use site, and drop the now-unnecessary crate-level
#![expect(clippy::absolute_paths)].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-17 17:10:29 +00:00
Henry Hrvoje Tonkovac
2a121b807e serial_buffer: trim qualified paths
Import the std modules used in the crate instead of spelling the full
paths at every use site, and drop the now-unnecessary
#[expect(clippy::absolute_paths)] on the test module.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-17 17:10:03 +00:00
Henry Hrvoje Tonkovac
c0e133bf05 vm-migration: trim qualified paths
Import the std modules used in the crate instead of spelling the full
paths at every use site, and drop the now-unnecessary crate-level
#![expect(clippy::absolute_paths)].

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-17 17:09:22 +00:00
Rob Bradford
e9f2f14e4c build: Add KVM SEV-SNP testing on MQ
Using a new dedicated runner do SEV-SNP testing on the MQ using the CVM
test suite.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-17 15:02:53 +00:00
Rob Bradford
4a88395a35 tests: Add missing preparatory steps needed for CVM tests
The tests expect some generated images and assets so make sure that they
are there (directly copied from the x86-64 test).

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-17 15:02:53 +00:00
Rob Bradford
e4a6208a88 build: Temporarily drop the MSHV CI jobs
These fail more than they pass due to infrastructure reasons (rather
than tests failing) and so because they are constantly seen as failing
they are ignored (i.e. alarm fatigue).

It is better not to run them until the infrastructure issues have been
fixed.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-17 14:59:22 +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
7ca99204ed vmm: cpu: Reject mis-sized ACPI CPU hotplug register accesses
Reject without asserting that the ACPI CPU hotplug register accesses
match those that are specified by the ACPI definitions.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:Opus-4.7
2026-06-17 18:06:21 +01:00
Rob Bradford
df09e80b89 vmm: device_manager: Reject mis-sized PCI hotplug register accesses
Reject without asserting that the ACPI PCI hotplug register request
matches what is defined in the ACPI definition.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:Opus-4.7
2026-06-17 18:06:21 +01:00
Rob Bradford
8a4b3efec9 devices: acpi: Reject mis-sized accesses to shutdown and GED devices
These devices should only be accessed by single byte accesses as
specified through the ACPI definitions for them.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:Opus-4.7
2026-06-17 18:06:21 +01:00
Meng Zhuo
14aa30cd2e vmm: retrieve timebase-frequency from KVM instead of hardcoding
The RISC-V device tree's timebase-frequency was hardcoded to 10 MHz
(0x989680). Actual hardware uses different frequencies.

Read the timebase frequency from KVM_GET_ONE_REG via
KVM_REG_RISCV_TIMER (offset 0, kvm_riscv_timer.frequency),
thread it through the VMM to arch to FDT layers, and fall back to
the 10 MHz default when KVM returns no value.

Signed-off-by: Meng Zhuo <mengzhuo@iscas.ac.cn>
2026-06-17 16:05:53 +01:00
Meng Zhuo
085642dd42 hypervisor: riscv64: clamp AIA SRCS to KVM device capacity
The number of wired interrupt sources (SRCS) must be less than the
KVM device's maximum interrupt identities (kvm_riscv_aia_max_ids).
Platforms with smaller IMSIC capacity reject values that exceed this
limit.

Query KVM_DEV_RISCV_AIA_CONFIG_IDS before setting SRCS and clamp the
requested value to the reported nr_ids (which equals max_ids - 1).

Signed-off-by: Meng Zhuo <mengzhuo@iscas.ac.cn>
2026-06-17 16:05:53 +01: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
Bo Chen
ca2f847e5f tests: Add integration test for FD-based VFIO device
This also covers the usage of pre-opened iommufd FD.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-06-17 08:39:43 +00:00
Bo Chen
19fb12c28b vmm: device_manager: Use externally-supplied iommufd FD
Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-06-17 08:39:43 +00:00
Bo Chen
867b5796a5 vmm: Accept an externally-opened iommufd FD
The CLI `--platform` option now accepts `iommufd_fd=<n>` alongside the
existing `iommufd=on|off`.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-06-17 08:39:43 +00:00
Bo Chen
e4934de3c9 ch-remote: Support FD-based VFIO devices
Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-06-17 08:39:43 +00:00
Bo Chen
0419ab6f39 vmm: OpenAPI: Make DeviceConfig path optional
`path` is no longer required in the DeviceConfig, since a VFIO
device may also be supplied via a pre-opened cdev FD passed via
SCM_RIGHTS alongside the /vm.add-device request.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-06-17 08:39:43 +00:00
Bo Chen
c315d5fd96 vmm: Enable FD-based VFIO devices
Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-06-17 08:39:43 +00:00
Bo Chen
d9f89ef2ab vmm: http_api: Accept a VFIO device FD via SCM_RIGHTS
Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-06-17 08:39:43 +00:00
Bo Chen
0e4b98ae8e vmm: DeviceConfig: Add fd field for an externally-opened vfio cdev
Add a new `fd: Option<i32>` field to DeviceConfig so a caller can
supply a pre-opened vfio cdev FD (e.g. /dev/vfio/devices/vfioN) in
addition to the existing sysfs path. The CLI `--device` option now
accepts `fd=<n>`, parsed alongside the existing options.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-06-17 08:39:43 +00:00
Bo Chen
bc5363823a vmm: DeviceConfig: Store path as Option<PathBuf>
Relax DeviceConfig::path from PathBuf to Option<PathBuf> in preparation
to accept an externally-opened vfio cdev FD. The parser and OpenAPI spec
still enforces that `path` is set, so callers see no behavior change.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-06-17 08:39:43 +00:00
Bo Chen
96ea24339d vmm: seccomp: Allow IOMMU_DESTORY ioctl
Update the seccomp filter for vmm and vcpu thread, because `Drop for
VfioIommufd` since vfio-ioctls v0.6.1 now issues IOMMU_DESTROY
to release the IOAS allocated for each VM boot.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-06-17 08:39:43 +00:00
Bo Chen
75e4a46969 build: Update rust-vmm dependencies in /fuzz
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-06-17 08:39:43 +00:00
Anatol Belski
fd4a923c37 virtio-devices: block: Test parse failure head reclamation
Submit a head only virtio-blk chain, confirm Request::parse rejects
it, and verify that add_used with len 0 returns the head to the
guest by advancing the used ring index.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-16 22:02:46 +00:00
Anatol Belski
e1a63b41ff virtio-devices: block: Reclaim head on malformed descriptor chain
When Request::parse failed, for example for a chain containing only
the head descriptor, process_queue_submit returned the error via
`?`. The caller process_queue_submit_and_signal swallowed
Error::RequestParsing with a warn! and returned Ok(()), but
queue.iter().next() had already consumed the head from the avail
ring. The head was never written to the used ring, so the descriptor
slot leaked and the queue could be stalled by a guest that keeps
submitting malformed chains.

Handle the parse error in line. Log a warning, add the head to the
used ring with len 0, reenable notifications, and continue draining
the queue. A VIRTIO_BLK_S_IOERR status cannot be written because the
status descriptor address is exactly what failed to parse.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-16 22:02:46 +00:00
Philipp Schuster
4491a3e412 vmm: improved error messages
Unrelated improvements that help to catch common pitfalls.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-16 22:01:30 +00:00
Philipp Schuster
15cab7ee6a vmm: improve VM ownership handling
Introducing a new enum that models the various states of VM ownership
from the perspective of the VMM.

This is an important prerequisite for the asynchronization of the
migration, where the ownership of the Vm struct is transferred to the
migration thread. Specifically, this allows to introduces a new
"Migration(ThreadHandle)" variant and all existing match statements
can be easily extended to react accordingly.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-16 22:01:30 +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
Rob Bradford
aad8ea0bd7 build: Bump some rust-vmm crates
Bump rust-vmm crates that don't require bumping vm-memory (which will
require broader porting).

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-16 13:09:48 +00:00
Henry Hrvoje Tonkovac
0e2e8d332a vm-virtio: trim qualified paths
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 11:07:02 +00:00
Henry Hrvoje Tonkovac
88baef1449 vm-device: trim qualified paths
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 11:02:03 +00:00
Henry Hrvoje Tonkovac
11bacdee46 vhost_user_block: trim qualified paths
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:54:05 +00:00
Henry Hrvoje Tonkovac
11fab725ae vhost_user_net: trim qualified paths
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:53:30 +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
Wei Liu
b51dfec09c tests: re-enable some MSHV tests
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-16 07:06:47 +00:00
tonic
ce9416a9c2 arch: x86_64: advertise mandatory Hyper-V partition privileges
The Microsoft "Requirements for Implementing the Microsoft Hypervisor
Interface" document marks exactly two privileges in CPUID leaf
0x40000003 EAX as "Must be set": AccessHypercallMsrs (bit 5) and
AccessVpIndex (bit 6). Cloud Hypervisor advertised neither.

Without bit 5, Windows guests abort enlightened-mode initialization
before timer-API selection: HalpHvTimerApi is left NULL and every
QueryPerformanceCounter call falls back to reading
HV_X64_MSR_TIME_REF_COUNT (0x40000020), costing one VM exit per call.
The guest never writes HV_X64_MSR_REFERENCE_TSC (0x40000021) to enable
the reference TSC page, even though AccessPartitionReferenceTsc (bit 9)
is advertised.

With both bits set, Windows 10 22H2 and Windows 11 25H2 guests enable
the reference TSC page at boot. Measured QueryPerformanceCounter
throughput on a nested-KVM host went from ~71K calls/sec (14 us/call,
one MSR exit each) to ~1.3M calls/sec (free, no exits); on bare metal
from ~390K to ~1.9M calls/sec. Guest idle CPU and interrupt-service
time drop correspondingly.

Both MSR ranges are already handled in-kernel by KVM unconditionally,
so no backend change is needed. Bisection across the full delta to
QEMU's Hyper-V CPUID layout (vendor ID, max leaf, leaves 4-6 contents,
build number) shows bit 5 is the only load-bearing change; bit 6 is
included per the conformance document's mandate.

Signed-off-by: Tonic Li <tonic@simular.ai>
Signed-off-by: tonic <tonicbupt@gmail.com>
2026-06-15 13:54:01 +00:00
Henry Hrvoje Tonkovac
fffa200240 rate_limiter: trim qualified paths
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-15 13:55:12 +01: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
d9f3400aba tracer: trim qualified paths
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-15 11:20:05 +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
Dylan Reid
50f2fd369f virtio-devices: block: drain async I/O before pausing
During pause the block backend's async I/O path can have unfinished I/O
requests. A snapshot or migration RAM copy taken after pause returns can
then race with kernel writes and capture torn pages.

Since vCPUs are already paused, the VMM thread can stop new block
submissions and wait for the worker to drain before parking the worker
threads.

Assisted-by: Codex:GPT-5
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-13 18:13:37 +00:00
Tushar Khatri
859bce5cae hypervisor: reevaluate #[allow] attributes
Remove stale #[allow]s whose lints no longer fire, convert the
unconditionally-firing ones to #[expect], and keep the conditional
ones as #[allow] (e.g. large_enum_variant only fires when both kvm
and mshv are enabled; a nonminimal_bool only on x86). The many
unreachable_patterns allows are feature-gated and left as #[allow].

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-13 19:11:27 +01:00
Tushar Khatri
d6e59a0be7 arch: reevaluate #[allow] attributes
Remove stale #[allow]s whose lints no longer fire (a module-level
non_camel_case_types in mpspec, and a too_many_arguments on a riscv64
configure_system that no longer exceeds the argument threshold), and
convert the still-needed ones to #[expect].

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-13 19:11:27 +01:00
Tushar Khatri
f5f74aaa29 devices: 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-13 19:11:27 +01:00
Rob Bradford
3b9229e434 build: Consolidate sev_snp feature usage
Since igvm is a required feature of sev_snp and also sev_snp is x86-64
only the cfg attributes at build time can be consolidated & simplified.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-12 22:29:26 +00:00
Rob Bradford
bf3bc325e2 vmm: Validate the PCI segment ID without platform configuration
Validate that the PCI segment specified is a valid PCI segment ID (less
than the number of segments specified) defaulting to default if no
segments are specified because no there is no platform configuration.

See: #8376

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-12 13:08:25 +00:00
Rob Bradford
e94bb6bce6 main: Fix test_vmm_vm_cold_add_user_device
This test has a copy and paste error where the PCI segment ID was being
set with no extra segments configured.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-12 13:08:25 +00:00
Rob Bradford
24f8ccf5a7 main: Fix test_valid_vm_config_serial_console
This test has a copy and paste error where the PCI segment ID was being
set with no extra segments configured.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-12 13:08:25 +00:00
Sebastian Eydam
3a01ad081a docs: document live migration TLS encryption
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
cf28552085 vmm: encrypt migration data with TLS if configured
Wire in the code paths that activate the TLS encrypting if the necessary
API arguments are provided.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
320403a11e vmm: validate TLS related files
Validate that all files that are necessary for TLS encryption are in the
given folder. The knowledge which files are necessary is part of the TLS
module.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
58baee16ac vmm: add TLS API option to receive migration call
As we now have more than one parameter for the receive migration call,
this commit also adds parsing and validation for those parameters. We
maintain backwards compatibility by also correctly parsing the case
where the caller only provides a URL.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
c23edda98b vmm: add TLS API option to send migration call
To enable TLS, the caller has to provide a path to a directory that
contains the necessary files.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
001bdde75f vmm: tighten migration URL validation
For TLS we have to parse the hostname from the given migration URL. For
that we have to make a few assumptions about the URL (e.g. it always has
a port). To catch problems early, we tighten the URL validation.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
0ec2ae376b vmm: accept migration connections over TLS
Extend ReceiveListener with a TLS-backed listener variant for migration
receivers.

Store the TCP listener together with the server TLS configuration, wrap
accepted sockets in TlsStream::new_server(), and preserver the existing
listener cloning and fd polling behavior so receive-side migration code
can treat TLS listeners like the existing TCP and UNIX cases.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
f3623e6403 vmm: add TLS streams to migration transport
Teach the migration transport to handle TLS-backed streams alongside
plain TCP and UNIX sockets.

Introduce a Tls variant in SocketStream and implement the necessary
traits.

Also updates the local-migration error path to reject any non-UNIX
transport, which now includes TLS-wrapped TCP connections.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
eaceef5aa0 vm-migration: server-side of a TLS connection
Code for the TLS server, i.e. the receiver of a live migration.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
8e9d779688 vm-migration: client-side of a TLS connection
TLS connections have a TLS server (listens for incoming connections) and
a TLS client (initiates the connection). This commit adds the code for
the client side, which is the sender of a migration

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
6e501cc2dc vmm: remove redundant SocketStream overrides
ReadVolatile already provides a default read_volatile_exact()
implementation, and WriteVolatile a default write_volatile_exact()
implementation. Overriding these functions adds no behavioral value, but
duplicates logic and needs to be updated whenever SocketStream gains or
changes a variant.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Sebastian Eydam
d181d2c2ac vmm: remove AsRawFd trait for SocketStream
The trait is not used and thus can be removed.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-06-12 10:11:56 +00:00
Rob Bradford
01593ad181 virtio-devices: vsock: Handle host half-close
When the host half-closed the socket this was wrongly interpreted as a
full shutdown preventing the guest from sending any more data. Instead
propagate the half-close by setting just `VSOCK_FLAGS_SHUTDOWN_SEND`,
leaving the connection alive so that guest-to-host writes are still
forwarded. The connection is only torn down once the guest also shuts
down its send side or a host write fails.

Fixes: #8300

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-12 08:20:00 +00:00
Rob Bradford
5eb539b6ef virtio-devices: vsock: Handle guest half-close
When the guest did a half-close (shutting down only its send side) the
connection state was updated but the write half of the host Unix socket
was never closed so the host peer never saw an EOF. This caused issues
with newer systemd (v256+) as it now half closes its socket and waits
for the host side to react and fully close the connection.

Propagate the guest's half-close to the host by shutting down the write
half of the backing stream. This is deferred until any buffered guest
data has been flushed so that no data is lost, and the connection is
left open so that host-to-guest data keeps flowing.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-12 08:20:00 +00:00
wuxinyue
75b0fe5a21 vmm: defer PCI device visibility to fix hotplug race condition
Split `add_pci_device()` into two phases: `allocate_pci_bars()` which
only allocates BAR address space, and `commit_pci_device()` which
makes the device visible to the guest on the PCI bus.

All callers now follow the pattern: allocate BARs → perform device-
specific setup (ioeventfd, device_tree, mmio mapping) → commit device.
This eliminates a race window where the guest could discover a
partially-initialized device via `acpiphp_check_bridge()` during rapid
sequential hotplug, causing BAR reprogramming to fail because
ioeventfds and device_tree entries were not yet in place.

Signed-off-by: wuxinyue <wuxinyue.wxy@antgroup.com>
Assisted-by: Claude:Opus-4.6
2026-06-12 08:19:42 +00:00
wuxinyue
f787bd7a17 vmm: move BAR mapping registration from PciBus to DeviceManager
`PciBus::register_mapping()` operates on `mmio_bus` and `io_bus`
which are passed in as external parameters and have nothing to do
with PciBus internal state. Move this logic into
`DeviceManager::register_bar_mapping()` where it belongs, and move
the `PioInsert`/`MmioInsert` error variants from `PciRootError` to
`DeviceManagerError` accordingly.

Signed-off-by: wuxinyue <wuxinyue.wxy@antgroup.com>
Assisted-by: Claude:Opus-4.6
2026-06-12 08:19:42 +00:00
dependabot[bot]
bf4b856dd2 build(deps): bump the non-rust-vmm group across 2 directories with 15 updates
Bumps the non-rust-vmm group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_with](https://github.com/jonasbb/serde_with) | `3.20.0` | `3.21.0` |
| [bitflags](https://github.com/bitflags/bitflags) | `2.11.1` | `2.12.1` |
| [log](https://github.com/rust-lang/log) | `0.4.30` | `0.4.32` |
| [zbus](https://github.com/z-galaxy/zbus) | `5.15.0` | `5.16.0` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.99` | `0.3.100` |

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

| Package | From | To |
| --- | --- | --- |
| [serde_with](https://github.com/jonasbb/serde_with) | `3.20.0` | `3.21.0` |
| [bitflags](https://github.com/bitflags/bitflags) | `2.11.1` | `2.12.1` |
| [log](https://github.com/rust-lang/log) | `0.4.30` | `0.4.32` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.99` | `0.3.100` |
| [libfuzzer-sys](https://github.com/rust-fuzz/libfuzzer) | `0.4.12` | `0.4.13` |



Updates `serde_with` from 3.20.0 to 3.21.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.20.0...v3.21.0)

Updates `bitflags` from 2.11.1 to 2.12.1
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.11.1...2.12.1)

Updates `log` from 0.4.30 to 0.4.32
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.30...0.4.32)

Updates `zbus` from 5.15.0 to 5.16.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus-5.15.0...zbus-5.16.0)

Updates `js-sys` from 0.3.99 to 0.3.100
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `serde_with_macros` from 3.20.0 to 3.21.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.20.0...v3.21.0)

Updates `wasm-bindgen` from 0.2.122 to 0.2.123
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.122...0.2.123)

Updates `wasm-bindgen-macro` from 0.2.122 to 0.2.123
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.122...0.2.123)

Updates `wasm-bindgen-macro-support` from 0.2.122 to 0.2.123
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.122...0.2.123)

Updates `wasm-bindgen-shared` from 0.2.122 to 0.2.123
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.122...0.2.123)

Updates `zbus_macros` from 5.15.0 to 5.16.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus_macros-5.15.0...zbus_macros-5.16.0)

Updates `zvariant` from 5.11.0 to 5.12.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant-5.11.0...zvariant-5.12.0)

Updates `zvariant_derive` from 5.11.0 to 5.12.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant_derive-5.11.0...zvariant_derive-5.12.0)

Updates `zvariant_utils` from 3.3.1 to 3.4.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant_utils-3.3.1...zvariant_utils-3.4.0)

Updates `serde_with` from 3.20.0 to 3.21.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.20.0...v3.21.0)

Updates `bitflags` from 2.11.1 to 2.12.1
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.11.1...2.12.1)

Updates `log` from 0.4.30 to 0.4.32
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.30...0.4.32)

Updates `js-sys` from 0.3.99 to 0.3.100
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `serde_with_macros` from 3.20.0 to 3.21.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.20.0...v3.21.0)

Updates `wasm-bindgen` from 0.2.122 to 0.2.123
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.122...0.2.123)

Updates `wasm-bindgen-macro` from 0.2.122 to 0.2.123
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.122...0.2.123)

Updates `wasm-bindgen-macro-support` from 0.2.122 to 0.2.123
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.122...0.2.123)

Updates `wasm-bindgen-shared` from 0.2.122 to 0.2.123
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.122...0.2.123)

Updates `libfuzzer-sys` from 0.4.12 to 0.4.13
- [Changelog](https://github.com/rust-fuzz/libfuzzer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-fuzz/libfuzzer/compare/0.4.12...0.4.13)

---
updated-dependencies:
- dependency-name: serde_with
  dependency-version: 3.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: bitflags
  dependency-version: 2.12.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: log
  dependency-version: 0.4.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zbus
  dependency-version: 5.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.100
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with_macros
  dependency-version: 3.21.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.123
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.123
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.123
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.123
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zbus_macros
  dependency-version: 5.16.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zvariant
  dependency-version: 5.12.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zvariant_derive
  dependency-version: 5.12.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zvariant_utils
  dependency-version: 3.4.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: serde_with
  dependency-version: 3.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: bitflags
  dependency-version: 2.12.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: log
  dependency-version: 0.4.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.100
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with_macros
  dependency-version: 3.21.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.123
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.123
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.123
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.123
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libfuzzer-sys
  dependency-version: 0.4.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-12 00:52:12 +00:00
Tushar Khatri
b059475dfb vmm: reevaluate #[allow] attributes
Remove stale #[allow]s whose lints no longer fire, convert the
unconditionally-firing ones to #[expect], and keep the
arch/feature-conditional ones as #[allow]. Verified across kvm/mshv,
x86_64/aarch64, and --all-features.

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-11 19:06:49 +00:00
Tushar Khatri
4f68b687aa virtio-devices: 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-11 19:06:49 +00:00
Tushar Khatri
712d42e6ac vm-device: reevaluate #[allow] attributes
Drop stale #[allow]s whose lints no longer fire and convert the
still-needed ones to #[expect] so they warn if the lints stop firing.

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-11 19:06:49 +00:00
Tushar Khatri
407dd12dc5 vm-virtio: reevaluate #[allow] attributes
Remove a stale #[allow(non_camel_case_types)] whose lint no longer
fires; the enum variants are already CamelCase.

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-11 19:06:49 +00:00
Dylan Reid
0487035512 vmm: release vIOMMU VFIO container on device eject
Currently ejecting a device leaks its mapping keeping the container fd
open. Remove the mapping so the fd can be closed.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-11 15:15:38 +00:00
Leander Kohler
686b8b40ec docs: document migration protocol versioning
Document how live migration protocol versions are handled, including
the supported current/previous version window and the need to migrate
through an intermediate Cloud Hypervisor version for larger version
gaps.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-06-11 13:24:16 +00:00
Leander Kohler
59e92f1972 main: print supported protocol versions
Print the supported vm-migration protocol version range in
cloud-hypervisor --version as an extra line:

  vm-migration protocol versions v0-v1

This makes the currently supported compatibility window
visible without having to inspect the migration code.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-06-11 13:24:16 +00:00
Leander Kohler
a098920e19 build, main: add vm-migration dependency
The cloud-hypervisor binary prints vm-migration protocol constants
directly, so it needs its own dependency on the vm-migration crate
instead of relying on vmm's transitive dependency.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-06-11 13:24:16 +00:00
Leander Kohler
51bd9d24f3 vmm, vm-migration: validate protocol version at start
Validate the sender's migration protocol version when
handling the initial Start request.

Read the version from the Start command header, accept only
the supported version window n-1..=n, and reject unsupported
versions with Error. A rejected Start moves the receiver to
the aborted state.

This keeps compatibility one-way, from older protocol
versions to newer ones, and leaves later version-based
branching on the receiver side.

Log the protocol version on both sender and receiver to make
the active migration path visible.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-06-11 13:24:16 +00:00
Leander Kohler
b27faaaa45 vm-migration: add protocol versioning
Add protocol-side support for migration protocol versioning.

Use the existing 6-byte Start command header, which was
previously zero padding, to carry the sender's migration
protocol version without changing the wire layout.

Store the version as a little-endian u16 in the first two
bytes and ignore the remaining four bytes. A zeroed command
header continues to mean a legacy v0 sender.

This keeps the message flow unchanged for rollout:
Start is still followed by plain OK or Error (Aborted), and no new
command is needed.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-06-11 13:24:16 +00:00
Henry Hrvoje Tonkovac
181d29ee90 devices: trim qualified paths
Import the modules used in the crate instead of spelling the
fully-qualified paths at every use site, and collapse Result<T,
io::Error> into io::Result<T>. This covers the feature-gated modules
(fw_cfg, ivshmem, pvmemcontrol) as well, leaving the whole crate free
of clippy::absolute_paths warnings.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-11 11:01:15 +00:00
Tushar Khatri
9eff92fb4b pci: reevaluate #[allow] attributes
Drop stale #[allow]s whose lints no longer fire and convert the
rest to #[expect], which warns if they ever stop being needed.

Part of #8326.

Signed-off-by: Tushar Khatri <hello@tusharkhatri.in>
2026-06-11 09:19:35 +00:00
Rob Bradford
7042922e83 vmm: Add a final memory pass after capturing snapshot
During migration send one final set of changed memory after capturing
the snapshot/state. This captures any memory changed as a side effect of
capturing that state. In particular with vhost-user capturing the device
state can lead to inflight requests being drained/flushed which could
change memory. As this is related to the snapshot account for this
memory transfer in the snapshot metrics.

No equivalent change is needed for snapshot as the memory is written
after the state is snapshotted.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-11 08:00:50 +00:00
Rob Bradford
c194f63cf4 virtio-devices: vhost_user: Save the dirty log before shutdown
Query and save the dirty log before shutting down the vhost-user device.
This allows any final dirty memory ranges to be recorded before it
becomes impossible to do that as the vhost-user handle has been closed.
This is required to ensure that all memory writes have been correctly
recorded that may be triggered by inflight I/O drains from vhost-user
device state capture.

One small implementation wrinkle: with local migrations there is no
dirty logging (since we just pass the memory FD over the socket) so
calling dirty_log() would generate an error. As there is no clean way to
query if dirty logging has been started add a boolean to track if its
active.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-11 08:00:50 +00:00
Rob Bradford
47e61f3b2a virtio-devices: Move guest_memory to VhostUserCommon
Rather than each device implementation holding a reference to the guest
memory move this to VhostUserCommon. This refactoring simplifies the
function signatures but also allows for methods that act on
VhostUserCommon that don't have the memory available to them.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-11 08:00:50 +00:00
Ruben Hakobyan
e9b47ebacd vmm: Make sev_snp depend on igvm
Currently both kvm and mshv require an IGVM file to boot a SEV-SNP VM.
This is already configured in the top-level cloud-hypervisor
Cargo.toml where sev_snp depends on igvm.

Add a similar dependency in the vmm crate which helps simplify some of
the in-code cfg blocks by removing the ones that are within a sev_snp
cfg block.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-06-10 21:29:40 +00:00
Henry Hrvoje Tonkovac
57b02c765f pci: trim qualified paths
Import the modules used in the crate instead of spelling the
fully-qualified paths at every use site. This covers std paths along
with a few crate-internal and external-crate paths, leaving pci free of
clippy::absolute_paths warnings.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.8
2026-06-10 20:47:59 +00:00
Wei Liu
027b1a4c46 tests: enable MSHV fw_cfg coverage
Re-enable the fw_cfg integration tests for MSHV now that port string I/O
is handled by the hypervisor backend.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:GPT-5.5
2026-06-10 20:34:45 +00:00
Wei Liu
b3149e87fd hypervisor: mshv: emulate string port I/O
Handle MSHV INS/OUTS port intercepts by translating the guest string
operand through MshvEmulatorContext and copying data between guest
memory and existing PIO callbacks.

Support REP counts, zero-count REP, and direction-flag based RSI/RDI
updates. Commit RIP plus RCX/RSI/RDI after the transfer completes.

This removes the fw_cfg/debug-port skip. OVMF can now use the real
string I/O path instead of relying on ignored ports.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:GPT-5.5
2026-06-10 20:34:45 +00:00
Wei Liu
cfc7775f80 hypervisor: mshv: add port I/O helpers
Decode MSHV port access size, direction, string, and REP state through
small helpers instead of open-coded bitfield reads.

Keep scalar I/O behavior unchanged and continue rejecting string I/O in
this step. Put the x86_64 port I/O helpers in their own module so the
string emulation path can grow without bloating the top-level MSHV code.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:GPT-5.5
2026-06-10 20:34:45 +00:00
Wei Liu
43138da0d7 hypervisor: x86: share string op helpers
Move common string instruction bookkeeping into the x86 instruction
emulator so MOVS, STOS, and MSHV port string I/O use one implementation
for REP counts, direction-flag handling, and index advancement.

This keeps existing MOVS/STOS behavior unchanged while removing the need
for MSHV to open-code the same string-operation details.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:GPT-5.5
2026-06-10 20:34:45 +00:00
Wei Liu
0cd68e8f3b hypervisor: mshv: refactor port I/O exits
Move scalar HVMSG_X64_IO_PORT_INTERCEPT handling into a helper so the
string I/O implementation can build on the same dispatch path.

Keep the existing fw_cfg/debug-port skip plus string/REP assertions in
place. This is only code movement so later changes are easier to review.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Copilot:GPT-5.5
2026-06-10 20:34:45 +00:00
Ian Klemm
e8e532faf3 tests: exercise memory reserve on the hugepage UFFD restore zone
Turn reserve=on for the hugepage-backed memory zone in the UFFD
snapshot/restore integration test. Hugepages are the most likely place
to want reserve (an over-committed huge page pool is exactly the case
that otherwise SIGBUSes the guest), so this is the natural test to give
the option real coverage, as suggested in review.

It exercises the reserve mmap path twice: once on the source VM boot and
once on the demand-paged restore. The existing skip guard already
requires the 256 free 2MiB pages this zone needs, and the source VM is
killed before the restore VM is started, so reserving from the pool
never has to back two VMs at once.

Assisted-by: Claude Code (Opus 4.8)
Signed-off-by: Ian Klemm <hi@ianklemm.de>
2026-06-10 12:30:25 +00:00
Ian Klemm
8d05407799 vmm: add memory reserve option to opt out of MAP_NORESERVE
Cloud Hypervisor maps guest RAM with MAP_NORESERVE, so the kernel never
reserves the backing pages at mmap time. On a host whose hugepage pool
cannot satisfy every guest, a VM is created successfully and then takes
a SIGBUS when the guest faults a page the pool can no longer back. This
is the failure mode reported in #5730 and #7387. As noted on #5730,
checking free pool headroom up front is not a reliable fix: another
process can consume pages between the check and the fault.

Add a reserve=on parameter to --memory and --memory-zone (default off,
preserving the current MAP_NORESERVE behaviour). When set, the region
is mapped without MAP_NORESERVE, so the kernel reserves the backing
pages (swap, or huge pages for hugepage-backed memory) at mmap time,
atomically with the mapping. An over-committed configuration then
fails cleanly at VM creation with an mmap ENOMEM instead of crashing
the guest later. Unlike prefault it does not fault the memory in, so
it does not slow down boot.

This mirrors QEMU's memory-backend reserve property, which has the same
name and meaning (reserve=off maps with MAP_NORESERVE). reserve is
threaded through the same mmap paths as the existing prefault option,
and is exposed in the OpenAPI schema, CLI help and docs. The top-level
--memory reserve=on path is unchanged: the default zone is synthesised
from MemoryConfig and inherits its reserve value.

Assisted-by: Claude Code (Opus 4.8)
Signed-off-by: Ian Klemm <hi@ianklemm.de>
2026-06-10 12:30:25 +00:00
Dylan Reid
38bee23d89 vmm: memory_manager: free memslot in remove_userspace_mapping
remove_userspace_mapping tears down the KVM mapping but never returns
the slot id to the allocator's free list. Call `free_memory_slot` to
avoid the leak.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-10 10:15:46 +00:00
Dylan Reid
a048fa982d devices: ivshmem: handle short BAR0 register reads
BAR0 can technially be read with 1 or 2 byte MMIO. Don't panic in that
case.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-10 08:49:23 +00:00
Henry Hrvoje Tonkovac
4b2a77b86e vm-migration: trim qualified paths
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-09 15:51:32 +00:00
Ruben Hakobyan
2fc37a3235 tests: enable KVM SEV-SNP confidential VM integration tests
Bring the confidential VM (CVM) integration tests up on the KVM SEV-SNP
backend in addition to MSHV. On KVM the IGVM is an Oak stage0 firmware
image and the guest kernel is supplied separately: stage0 reads the
kernel, cmdline and E820 over fw_cfg. The test harness selects this
model when a guest kernel is present at /igvm_files/bzImage, mirroring
how the stage0 IGVM is discovered; MSHV keeps using the monolithic IGVM
with the kernel baked in.

  - test_infra: stage0 + direct-kernel + fw_cfg boot wiring (both the
    command line and the HTTP/D-Bus API path) plus an on_kvm_sev_snp()
    helper for tests to branch on.
  - tests: the CVM tests that don't work on the KVM SEV-SNP path yet are
    gated with #[cfg(not(feature = "kvm"))] inside the common_cvm module.
    The MSHV build enables mshv,igvm,sev_snp (no kvm feature) while the
    KVM build enables kvm,igvm,sev_snp,fw_cfg, so the cfg compiles these
    tests into the MSHV binary only and drops them on KVM; both
    hypervisors run the single common_cvm nextest profile. They all still
    run on MSHV:
      * test_pci_multiple_segments - stage0 places all 64-bit BARs in a
        single global window, so a BAR allocated in a different
        per-segment window is relocated cross-window and wedges boot.
      * test_dmi_uuid / test_dmi_oem_strings /
        test_dmi_system_and_chassis - SMBIOS is not delivered to SEV-SNP
        guests on the KVM stage0 boot path, so the guest's DMI tables
        read empty. VMM follow-up.
      * test_vdpa_block - needs host vdpa_sim_blk setup, and vDPA DMA
        into SEV-SNP-encrypted memory is unsupported (the guest hangs).

Assisted-by: Claude:Opus-4.8
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-06-09 15:49:42 +00:00
Ruben Hakobyan
b1d33ec9aa vmm: vm_config: apply serde defaults to FwCfgConfig
FwCfgConfig already has a Default impl (e820/kernel/cmdline/initramfs/
acpi_tables = true, items = None), but deserialization did not use it:
without serde default every field was mandatory, so any caller building
a payload config over the API had to spell out the whole object even to
flip a single flag.

Add a container-level #[serde(default)] so missing fields fall back to
FwCfgConfig::default(). The container form is required here because the
defaults are all true; a per-field #[serde(default)] would resolve bool
to false and contradict the Default impl.

This lets callers send only the fields that differ from the defaults.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-06-09 15:49:42 +00:00
Ruben Hakobyan
1071a3d301 test_infra: improve guest MemTotal assertion message
validate_memory() asserts that the guest's reported MemTotal exceeds the
expected size, but on failure printed nothing about either value.
Include both the actual and expected figures in the panic message so a
failing run is self-explanatory.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-06-09 15:49:42 +00:00
Ruben Hakobyan
ec157d7eb9 seccomp: allow openat + read on the HTTP API thread under KVM SEV-SNP
The KVM SEV-SNP net-hotplug integration tests (supported added in
later commits) intermittently kill the VMM with SIGSYS on the
http-server thread. The thread is seen reading
/proc/sys/vm/overcommit_memory (openat + read).

Allow the syscalls there, gated on sev_snp+kvm. seccomp can't match
a path, so the open is restricted to O_RDONLY.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-06-09 15:49:42 +00:00
Ruben Hakobyan
dc6a56a0db vmm: vm_config: allow opening /dev/sev after landlock
On KVM, an SEV-SNP guest opens /dev/sev at VM creation and passes
its fd as kvm_sev_cmd.sev_fd. KVM uses it to issue the SNP_LAUNCH_*
commands. The open happens after the landlock ruleset is applied,
so grant rw access to /dev/sev when the sev_snp platform feature
is enabled.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-06-09 15:49:42 +00:00
Ruben Hakobyan
9e6c817192 virtio-devices: add VIRTIO_F_ACCESS_PLATFORM to watchdog and iommu
A confidential guest (e.g. SEV-SNP) requires every virtio device to
advertise VIRTIO_F_ACCESS_PLATFORM so the guest driver routes DMA
through the platform's bounce-buffer path; the driver refuses a device
that does not offer it.

Add VIRTIO_F_ACCESS_PLATFORM support to virtio-{watchdog,iommu} which
are exercised as part of the CVM integration tests.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-06-09 15:49:42 +00:00
Henry Hrvoje Tonkovac
2b71ffd48e api_client: trim qualified paths
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-09 14:37:04 +00:00
Henry Hrvoje Tonkovac
b7526ec069 option_parser: trim qualified paths
Import std::fmt and std::result 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-09 12:45:21 +00:00
Muminul Islam
724ed2a4f2 README: update guest kernel version to ch-6.16.9
The guest kernel build instructions in the README referenced the
ch-6.12.8 branch, which was inconsistent with the version used by the
test scripts (scripts/test-util.sh uses ch-6.16.9). Update the README
to point at ch-6.16.9 so documentation and CI stay in sync.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-06-09 09:35:01 +01: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
cf55698168 fuzz: qcow: Switch fuzz to using QcowDisk
Fuzzing QcowFile, which is not used by virtio-block, is less helpful
than using the abstraction that is. Ensure we cover at least the same
functionality as before.

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
Max Makarov
7f6df9e870 tests: drain the replayed serial backlog and stop the pty echo loop
With socket serial output now buffered and replayed on connect, a
late-connecting client receives the whole boot backlog. The pty
interaction test had three problems with that:

- pty_read() slept a second between 512-byte reads and the loop consumed
  one chunk per two-second tick, far too slow to drain the backlog. Read
  in larger chunks without the per-read sleep and drain everything
  available each round; bound the loop so a missing marker can't run to
  the harness timeout.

- it wrote the login keystrokes before reading, so the unread backlog
  back-pressured the sender and the keystrokes never reached the prompt.
  Start reading concurrently with typing instead.

- the socat pty was created with echo on, so the replayed backlog was
  echoed back to the guest as serial input, flooding it (UART input
  overrun, login never completing). Create the pty with echo=0.

Signed-off-by: Max Makarov <maxpain@linux.com>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
2026-06-08 10:21:04 +00:00
Max Makarov
9889f6d403 vmm: buffer socket serial output for late-connecting clients
In Socket serial mode the device output sink was only installed once a
client connected, so output produced beforehand (kernel boot messages,
cloud-init) was dropped, and a client attaching after boot saw a blank
screen. Only PTY mode wrapped the sink in a SerialBuffer.

Install a persistent SerialBuffer as the Socket device's output sink at
SerialManager construction (discarding downstream via io::sink() until a
client connects), so output is captured into the 1 MiB ring even with no
client attached. On connect, retarget the buffer at the accepted client
and flush the backlog before live output resumes; on disconnect, keep
buffering so output produced while no client is attached is delivered to
the next one. The accepted socket is made non-blocking via
set_nonblocking() so a slow client cannot stall the vCPU thread
(SerialBuffer re-buffers on WouldBlock).

The serial-manager thread gains two syscalls under seccomp: sendto
(replaying the backlog is the first time it writes to the socket) and
ioctl restricted to FIONBIO, which is what set_nonblocking() issues.

Fixes: #7907

Signed-off-by: Max Makarov <maxpain@linux.com>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
2026-06-08 10:21:04 +00:00
Max Makarov
3955fdd22a serial_buffer: add set_out() to retarget the downstream writer
SerialBuffer owns its downstream writer privately, with no way to
replace it. Buffering the Socket console requires keeping one buffer
alive across client connects and disconnects and pointing it at each
newly accepted client (or a discarding sink when none is connected)
without dropping bytes buffered while no client was attached.

Add set_out(), which swaps the writer while leaving the buffered
contents intact, plus unit tests covering accumulate-while-detached,
replay on connect, live pass-through, delivery of while-detached output
to the next client, and that bytes already drained by one client are not
resent to the next.

Signed-off-by: Max Makarov <maxpain@linux.com>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
2026-06-08 10:21:04 +00:00
Henry Hrvoje Tonkovac
89afb088ec serial_buffer: trim qualified paths
Import std::io 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-08 09:28:19 +00:00
Henry Hrvoje Tonkovac
0b3af8aed2 net_util: trim qualified paths
Import the std modules used across 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-08 09:27:29 +00:00
Nikolas Kyx
4b671954a0 hypervisor: kvm: Support pre-XSAVE x86 CPUs
KVM will emulate XSAVE for us, so we need only to skip XCRS
setting/retrieval if the respective CPU feature is not available.

Signed-off-by: Nikolas Kyx <55556836+nyx191@users.noreply.github.com>
2026-06-06 11:39:47 +00:00
Dylan Reid
dd3a2f2649 virtio-devices: block: make shutdown join the worker
eject_device calls shutdown, but Block doesn't implement it, so the
worker thread was never joined. Drop for Block does not help either,
VirtioPciDevice keeps an Arc<Mutex<Block>> past eject, so Drop never
runs. The async worker keeps completing in-flight I/O into the guest RAM
that backed it when issued.

Implement shutdown() to call wait_for_epoll_threads(), which drops the
WorkerThreads handle and ensures that worker's io completes.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-05 08:00:10 +00:00
dependabot[bot]
2c702645d1 build(deps): bump the non-rust-vmm group across 2 directories with 10 updates
Bumps the non-rust-vmm group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.1` | `1.23.2` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.48` | `0.8.50` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.62` | `1.2.63` |
| libredox | `0.1.16` | `0.1.17` |
| [libz-sys](https://github.com/rust-lang/libz-sys) | `1.1.28` | `1.1.29` |
| [memchr](https://github.com/BurntSushi/memchr) | `2.8.0` | `2.8.1` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.25.11+spec-1.1.0` | `0.25.12+spec-1.1.0` |
| [typenum](https://github.com/paholg/typenum) | `1.20.0` | `1.20.1` |

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

| Package | From | To |
| --- | --- | --- |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.1` | `1.23.2` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.48` | `0.8.50` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.62` | `1.2.63` |
| [memchr](https://github.com/BurntSushi/memchr) | `2.8.0` | `2.8.1` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.25.11+spec-1.1.0` | `0.25.12+spec-1.1.0` |
| [typenum](https://github.com/paholg/typenum) | `1.20.0` | `1.20.1` |



Updates `uuid` from 1.23.1 to 1.23.2
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.1...v1.23.2)

Updates `zerocopy` from 0.8.48 to 0.8.50
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.48...v0.8.50)

Updates `cc` from 1.2.62 to 1.2.63
- [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.62...cc-v1.2.63)

Updates `libredox` from 0.1.16 to 0.1.17

Updates `libz-sys` from 1.1.28 to 1.1.29
- [Release notes](https://github.com/rust-lang/libz-sys/releases)
- [Commits](https://github.com/rust-lang/libz-sys/compare/1.1.28...1.1.29)

Updates `memchr` from 2.8.0 to 2.8.1
- [Commits](https://github.com/BurntSushi/memchr/compare/2.8.0...2.8.1)

Updates `shlex` from 1.3.0 to 2.0.1
- [Changelog](https://github.com/comex/rust-shlex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/comex/rust-shlex/commits)

Updates `toml_edit` from 0.25.11+spec-1.1.0 to 0.25.12+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.25.11...v0.25.12)

Updates `typenum` from 1.20.0 to 1.20.1
- [Release notes](https://github.com/paholg/typenum/releases)
- [Changelog](https://github.com/paholg/typenum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/paholg/typenum/compare/v1.20.0...v1.20.1)

Updates `zerocopy-derive` from 0.8.48 to 0.8.50
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.48...v0.8.50)

Updates `uuid` from 1.23.1 to 1.23.2
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.1...v1.23.2)

Updates `zerocopy` from 0.8.48 to 0.8.50
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.48...v0.8.50)

Updates `cc` from 1.2.62 to 1.2.63
- [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.62...cc-v1.2.63)

Updates `memchr` from 2.8.0 to 2.8.1
- [Commits](https://github.com/BurntSushi/memchr/compare/2.8.0...2.8.1)

Updates `shlex` from 1.3.0 to 2.0.1
- [Changelog](https://github.com/comex/rust-shlex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/comex/rust-shlex/commits)

Updates `toml_edit` from 0.25.11+spec-1.1.0 to 0.25.12+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.25.11...v0.25.12)

Updates `typenum` from 1.20.0 to 1.20.1
- [Release notes](https://github.com/paholg/typenum/releases)
- [Changelog](https://github.com/paholg/typenum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/paholg/typenum/compare/v1.20.0...v1.20.1)

Updates `zerocopy-derive` from 0.8.48 to 0.8.50
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.48...v0.8.50)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.23.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.63
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libredox
  dependency-version: 0.1.17
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libz-sys
  dependency-version: 1.1.29
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: memchr
  dependency-version: 2.8.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: shlex
  dependency-version: 2.0.1
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.25.12+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: typenum
  dependency-version: 1.20.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.50
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.23.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.63
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: memchr
  dependency-version: 2.8.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: shlex
  dependency-version: 2.0.1
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.25.12+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: typenum
  dependency-version: 1.20.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.50
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-05 00:49:37 +00:00
Andrei Vagin
f9709d6f92 ch-remote: Fix error message deserialization from JSON response
server_api_error_display_modifier deserialized the JSON error response
into a `Vec<&str>`.  However, if the error message contained escaped
characters, it could not deserialize it into a borrowed string `&str`
because unescaping requires allocation. This resulted in a
deserialization error and a failure to print the error chain.

This change switches the deserialization target to `Vec<String>` to
allow allocation.

Signed-off-by: Andrei Vagin <avagin@google.com>
2026-06-04 10:16:29 +00:00
Andrei Vagin
1b0dfc0da3 ch-remote: Make snapshot and restore config arguments required
The snapshot and restore subcommands in ch-remote had optional
snapshot_config and restore_config arguments, but the implementation was
unconditionally unwrapping them.

This change marks these arguments as required to handle the missing
argument validation and report a proper error message instead of letting
the application panic.

Signed-off-by: Andrei Vagin <avagin@google.com>
2026-06-04 10:16:29 +00:00
Aastha Rawat
14717a94a3 ci: implement SKU capacity & quota validation for mshv workflow
Prevent `SkuNotAvailable` errors for mshv workflow by checking capacity
restrictions for each location. Enhance the VM provisioning logic to
validate resource availibility before deployment.

Signed-off-by: Aastha Rawat <aastharawat@microsoft.com>
2026-06-04 10:59:15 +01:00
dependabot[bot]
62c8f71287 build(deps): bump crate-ci/typos from 1.47.1 to 1.47.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.47.1 to 1.47.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](44e2070e60...37bb98842b)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.47.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 04:27:33 +00:00
Muminul Islam
d83ffd7d7d docs: document hypervisor auto-detection in testing guide
Describe --hypervisor as an optional override that defaults to
auto-detection from the host device node (/dev/mshv or /dev/kvm) for
both the build and tests commands and the shared test-script arguments.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-06-04 01:46:36 +00:00
Muminul Islam
bbaeae7cb0 github: rely on hypervisor auto-detection for mshv CI
Drop the explicit --hypervisor mshv from the mshv integration workflow.
The runner exposes /dev/mshv, so dev_cli.sh now selects MSHV through
auto-detection.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-06-04 01:46:36 +00:00
Muminul Islam
5bed1600b4 scripts: auto-detect hypervisor in dev_cli.sh
Make the --hypervisor argument optional for the build and tests
commands. Add detect_hypervisor_device() and resolve_hypervisor_device()
helpers that map an explicit kvm/mshv name to its device node, or fall
back to probing /dev/mshv and /dev/kvm on the host when the flag is
omitted. The resolved device is validated before it is mounted into the
container, and an explicit --hypervisor is still forwarded to the test
scripts.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-06-04 01:46:36 +00:00
Muminul Islam
5932b11f13 scripts: auto-detect hypervisor in test-util.sh
Make the --hypervisor argument optional in process_common_args().
When it is not supplied, detect_hypervisor() now selects the backend
from the host device node: /dev/mshv for MSHV, /dev/kvm for KVM, and
errors out when neither is present. An explicit --hypervisor still
overrides the detection.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-06-04 01:46:36 +00:00
Dylan Reid
b88d5de85e main: logger: capture local timezone before seccomp
Local-time log fields called `jiff::Zoned::now()`, which resolves the
system timezone on every record by reading
`/etc/localtime`/`/etc/timezone` if it doesn't hit the cached version.
This cache miss could then cause a seccomp violation depending on the
thread it was run from.

Avoid this by capturing the value in `Logger`. This avoids opening the
seccomp filter for the whole process.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-03 20:38:54 +00:00
Daniel Verkamp
56e891a405 hypervisor: kvm: preserve kvmclock realtime and fill if needed
If `KVM_GET_CLOCK` already filled out the `realtime` field, it sets the
`KVM_CLOCK_REALTIME` flag, but if we instead preserve this flag, the
kernel will automatically adjust the kvmclock clock when calling
`KVM_SET_CLOCK` based on the elapsed wall-clock time between pause and
resume. This just requires removing the `reset_flags()` function, which
allows the `KVM_CLOCK_REALTIME` flag to persist in the serialized clock
state.

However, the kernel does not always fill the `realtime` field, depending
on clock source; in this case, fill `realtime` during pause based on the
system time. This is not as precise as the automatic `KVM_GET_CLOCK`
version, since we query the time slightly after the vCPU was paused, but
it allows the clock to be resumed mostly in sync instead of being wildly
off. In this case, we also set the `KVM_CLOCK_REALTIME` flag in the
saved `struct kvmclock` so `KVM_SET_CLOCK` will adjust the clock on
resume.

Basic test case:

1. Run a VM with a Linux guest.
2. Pause the guest via `vm.pause` API.
3. Wait several minutes.
4. Resume the guest via `vm.resume` API.
5. Verify the guest time (e.g. via `date` command) is valid.
6. Verify guest is still using `kvm-clock` timesource:

   cat /sys/devices/system/clocksource/clocksource0/current_clocksource

Before applying the patch, the guest clock would be off by the delta
time between pause and resume; after the patch, the clock is (more or
less) in sync with the correct wall-clock time.

Old snapshots will not have the `KVM_CLOCK_REALTIME` flag populated, so
they will not be affected by the new behavior.

Signed-off-by: Daniel Verkamp <drv@meta.com>
2026-06-03 20:32:58 +00:00
Max Makarov
81f9cd068f main: only remove the API socket after a clean run
The API socket path was removed unconditionally when the process
exited, including on a failed start. On a failed start where another
running instance already held the path, that deleted the live
instance's socket.

Remove the socket only when start_vmm returned Ok, meaning this process
owned and bound it. A stale socket left by a crash is cleaned up under
the lock by the next start, so dropping the unconditional removal does
not leak sockets, and a failed start no longer clobbers a socket owned
by another instance.

Signed-off-by: Max Makarov <maxpain@linux.com>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
2026-06-03 19:42:35 +00:00
Max Makarov
0a08f6551a vmm: clean up a stale API socket under a lock before bind
When Cloud Hypervisor crashed or was killed, the API socket file was
left on disk, so the next start failed with EADDRINUSE ("Address already
in use") and the VMM could not restart. This affects any environment
where the socket directory survives across restarts (systemd services,
Kubernetes emptyDir volumes, and so on).

Before binding the path-based API socket, take an exclusive lock on a
sidecar "<socket>.lock" file using the block crate's OFD-lock helper.
Holding it proves no other instance is bound to this path, so a stale
socket left by a crashed run can be removed safely and race-free. If
the lock is already held, fail with a clear "API socket is already in
use" error instead of clobbering the live instance. The lock is held
for the process lifetime and released by the kernel on exit or crash.
The fd-based (socket-activation) path is left unchanged.

This implements the lock-file approach suggested by @DemiMarie.

Fixes: #7784

Signed-off-by: Max Makarov <maxpain@linux.com>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
2026-06-03 19:42:35 +00:00
Dylan Reid
6219613bce vmm: allow madvise in the event-monitor seccomp filter
Fix racy seccomp kill on shutdown. When a VM shuts down the
event-monitor thread's recv() loop ends and the thread exits. glibc's
thread teardown then runs __malloc_arena_thread_freeres, which trims the
per-thread malloc arena with madvise(MADV_DONTNEED).

Add madvise to the allowed calls to match other threads. The crash is
intermittent because it only fires when that thread's arena accumulated
trimmable memory by shutdown.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-03 19:05:46 +00:00
dependabot[bot]
d53e3955b3 build(deps): bump crate-ci/typos from 1.47.0 to 1.47.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.47.0 to 1.47.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](f8a58b6b53...44e2070e60)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.47.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-03 18:30:34 +00:00
Philipp Schuster
13e727efed virtio-devices: vmm: replace #[allow(unnused)] with expect()
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
360e155cac vmm: replace #[allow(dead_code)] with expect()
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
619f964747 virtio-devices: replace #[allow(dead_code)] with expect()
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
0a3f594f4f rate_limiter: replace #[allow(dead_code)] with expect()
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
04322c185c pci: replace #[allow(dead_code)] with expect()
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
179d56ca72 hypervisor: replace #[allow(dead_code)] with expect()
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
9a60472ace devices: replace #[allow(dead_code)] with expect()
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
f1f02e7144 vmm: 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
c348d6598e vm-virtio: 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
6745ff87ef virtio-devices: 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
42fc9fd151 vhost_user_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
4a6add8808 performance-metrics: 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
9fb09bd35b pci: 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
5da4406a87 devices: 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
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
Philipp Schuster
d840b6dca1 performance-metrics: remove unused dependencies
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
760b0962c6 pci: remove 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
1f4b1f60a9 devices: remove unused 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
0f12650c02 main: remove unused dependencies
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
212986f013 vmm, docs: make PCI BDF configurable for ivshmem
Add shared PCI config to ivshmem.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-03 13:54:56 +00:00
Philipp Schuster
5aa0587f2a vmm: make PCI BDF configurable for balloon
Add shared PCI config to virtio-balloon.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-03 13:54:56 +00:00
Max Makarov
08bd7727ff vmm: omit unset Option fields from API responses
VmConfig and its nested configuration structs, the VmInfoResponse
wrapper and DeviceNode serialize their Option<T> fields as JSON null
when unset. The OpenAPI specification types these fields as
non-nullable, so strict client generators (for example ogen for Go)
reject /vm.info responses and cannot generate a working API client.

Apply serde_with's skip_serializing_none to the affected structs so
that unset optional fields are omitted from the serialized JSON instead
of being emitted as null. API responses now validate against the
existing specification unchanged; no nullable annotations are required.

Fixes: #7775

Signed-off-by: Max Makarov <maxpain@linux.com>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
2026-06-03 15:47:38 +01:00
Max Makarov
d595856748 vmm: return 404 for API requests against a non-created VM
The HTTP API mapped every ApiError to 500 Internal Server Error, so an
API client could not distinguish "the VM has not been created yet" from
a genuine server-side failure without parsing the error message text.

Derive the HTTP status code from the error itself in error_response():
errors whose root cause is VmError::VmNotCreated or VmMissingConfig are
now reported as 404 Not Found, regardless of which API action surfaced
them. The existing 400 (bad request) and 429 (too many requests)
mappings are preserved.

State-conflict errors such as VmNotRunning would ideally map to 409
Conflict, but micro_http's StatusCode has no Conflict variant, so they
remain 500 for now.

Fixes: #7774

Signed-off-by: Max Makarov <maxpain@linux.com>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
2026-06-03 12:37:51 +00:00
Anatol Belski
a89600aeba virtio-devices: Test activator success path
Feed Ok into VirtioPciDeviceActivator and assert that activate returns
Ok, device_activated becomes true, DEVICE_NEEDS_RESET is not set,
status is otherwise unchanged, no Config interrupt is delivered, and
the barrier waiter unblocks normally.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-02 14:49:32 +01:00
Anatol Belski
64c552cc96 virtio-devices: Test activator failure releases barrier
Feed BadActivate into VirtioPciDeviceActivator and assert that the
error propagates, device_activated stays false, DEVICE_NEEDS_RESET is
set in status, a single Config interrupt is delivered, and a thread
waiting on the activation barrier unblocks. The barrier release is
the deadlock fixed by the NEEDS_RESET on activation failure change.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-02 14:49:32 +01:00
Anatol Belski
7cdb724346 virtio-devices: Add activator unit test scaffolding
Add TestVirtioDevice with a controllable ActivateResult,
TestVirtioInterrupt that records delivered interrupt types, and a
make_activator helper that builds a complete VirtioPciDeviceActivator
with observable status, activated flag, interrupt log, and barrier.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-02 14:49:32 +01:00
Anatol Belski
2d2931a76e virtio-devices: vmm: Signal NEEDS_RESET on activation failure
When the guest writes DRIVER_OK and the device fails to activate, the
VMM previously bubbled the error up via VirtioActivate and never
released the activation barrier, leaving the vCPU that wrote DRIVER_OK
blocked on the barrier and effectively deadlocking the guest.

Per virtio 1.3 section 2.1.2, a device that has experienced an error
it cannot recover from should set DEVICE_NEEDS_RESET in its status and
notify the driver via a configuration change interrupt. Do that on
activation failure through the existing mark_device_needs_reset
helper, then release the activation barrier so the vCPU can resume.

DeviceManager::activate_virtio_devices now logs and continues instead
of aborting the whole pending list, so one failing device does not
take down the VMM or block pause and migration. The activator has
already reported the failure with the device id.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-06-02 14:49:32 +01:00
Wei Liu
d92e1ea77b tests: add Windows TPM integration test
Boot Windows with vTPM enabled and verify the TPM device enumerates
after the guest is reachable.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-02 09:18:44 +00:00
Wei Liu
4527ae449b tests: exercise TPM after reboot
Extend test_tpm to issue random, fixed-property, PCR read, and PCR
event commands before and after a guest reboot.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-02 09:18:44 +00:00
Wei Liu
8d0dc52cfa tpm: add startup response tests
Cover the accepted TPM2_Startup(CLEAR) response codes so the swtpm
reset handling keeps tolerating already-initialized TPMs.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-02 09:18:44 +00:00
Wei Liu
f4f5fcc06d devices: add TPM CRB regression tests
Cover the short CRB register accesses and data-buffer boundary
conditions used by Windows Server 2025.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-02 09:18:44 +00:00
Wei Liu
934910b94d tpm: refactor startup response check
Extract the TPM2_Startup(CLEAR) response-code check so the accepted
swtpm reset results can be covered directly.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-02 09:18:44 +00:00
Wei Liu
df41b03221 devices: refactor TPM CRB helpers
Extract CRB completion, register-read, and data-buffer range handling
so the fixed access rules can be tested without a live TPM backend.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-02 09:18:44 +00:00
Wei Liu
f91b748253 devices: lower a TPM log line to debug level
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-02 09:18:44 +00:00
Wei Liu
d262c81210 tpm: fix swtpm reset handling
Windows reboot recreates the TPM device while the swtpm process keeps
running. Leaving the transferred data fd open made a later CmdSetDatafd
fail, and the backend could remain unstarted after CmdInit.

Close both ends on setup failure, close the local transferred fd after
success, close the data fd on drop, and issue TPM2_Startup(CLEAR) after
CmdInit while tolerating an already-started TPM.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-02 09:18:44 +00:00
Wei Liu
7d237b5e20 devices: fix TPM CRB register byte-sized access
Windows can access CRB registers with byte-sized writes and reads. The
TPM device model used the byte offset as a u32 register index, which
corrupted unaligned accesses and could expose invalid CRB state.

Preserve the containing register on partial writes, read from the
correct byte lane, allow exact-end buffer accesses.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-02 09:18:44 +00:00
Philipp Schuster
727b704606 virtio-devices: vsock: improve error handling
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-02 09:17:12 +00:00
Philipp Schuster
cfc639de35 virtio-devices: vsock: remove dead code
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-02 09:17:12 +00:00
Philipp Schuster
3fa29920e8 vmm: preserve error chain for invalid memory path
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-02 09:17:12 +00:00
Philipp Schuster
f607d0143d arch: preserve error chain for memmap table
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-02 09:17:12 +00:00
Philipp Schuster
67a661aff0 arch: preserve error chain for smbios
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-02 09:17:12 +00:00
Philipp Schuster
85109ebae0 vm-migration: preserve error chain
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-02 09:17:12 +00:00
Philipp Schuster
b4c37def26 hypervisor: improve error chain
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-02 09:17:12 +00:00
Philipp Schuster
aa898db8d7 devices, vmm: improve error chain for ivshmm
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-06-02 09:17:12 +00:00
Dylan Reid
778c9f53dc virtio-devices: vsock: validate packet len on commit
RX packet assembly checks descriptor capacity before the backend sees
the packet. The backend then updates hdr.len before the header is
written back.

Validate that final length before committing the header, so we never
tell the guest that more bytes were written than fit in the RX buffer.

Assisted-by: Codex:GPT-5
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-01 18:54:03 +00:00
Dylan Reid
c6ecc13d08 virtio-devices: vsock: use volatile packet I/O
Remove the need for unsafely materializing slices from guest memory
pointers which is, by definition, undefined behavior.

Achieved by introducing a TxBufSource trait that is implemented for both
types of sources (Guest Memory or local copy) and by using the volatile
read/write primities for moving data from a readable or writable to
guest memory.

Assisted-by: Codex:GPT-5
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-01 18:54:03 +00:00
Dylan Reid
44f0360d28 virtio-devices: vsock: add volatile packet I/O
Keep packet data as a checked guest memory range and add helpers for
volatile reads and writes. Arguably VsockPacket should hold a
VolatileSlice for the guest memory usecase, but the lifetime tracking
involved wasn't worth it.

Keep the old slice accessors for now so existing callers still build.
The next commit switches them over.

Assisted-by: Codex:GPT-5
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-01 18:54:03 +00:00
Dylan Reid
e08f70b6d3 virtio-devices: vsock: drop unsafe test len helper
The helper only needs to update the packet len field. Use write_slice()
instead of rebuilding a mutable slice from a raw host pointer.

Assisted-by: Codex:GPT-5
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-06-01 18:54:03 +00:00
Wei Liu
dfcc02f547 tests: reenable TPM test for MSHV
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-01 17:46:00 +00:00
Wei Liu
d834c85697 tpm: rename established_flag to established_bit and match TCG semantics
The helper used to communicate the TPM Establishment bit between the
swtpm backend and the CRB device had inverted semantics:

    self.established_flag = est.resp.bit == 0;

so `established_flag == true` actually meant "*not* established". The
device-side call site then double-negated:

    if !self.emulator.get_established_flag() {
        val |= 0x1;  // tpmEstablished in TPM_LOC_STATE
    }

The end-to-end behaviour was correct but the boundary between the
swtpm-specific backend and the (TCG-spec defined) CRB device was hard
to follow and easy to misuse -- the now-removed pre-init check in
Emulator::new() was an example of that confusion (it errored out with
"TPM not in established state" precisely when the TPM *was*
established).

Per the TCG PC Client Platform TPM Profile (PTP) specification, bit 0
of TPM_LOC_STATE_x is `tpmEstablished`:

  * 0 = default state after a cold reset
  * 1 = a TPM2_Startup from Locality 3 or 4 has occurred

Rename the backend accessor to `get_established_bit()` and return the
bit value directly (true == 1, false == 0). The CRB device then simply
forwards the bit, with no inversion, which makes the spec mapping
obvious and removes swtpm-flavoured naming from the device layer.

No functional change.

Asissted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-01 17:46:00 +00:00
Wei Liu
746b760f69 tpm: remove spurious TPM-establishment check at startup
Emulator::new() refused to start the VMM unless the TPM Establishment
bit (TPM_LOC_STATE.tpmEstablished, bit 0) was already set, aborting
with "TPM not in established state" otherwise.

That gate is not justified by the TCG PC Client Platform TPM Profile
(PTP) specification:

  * tpmEstablished == 0 is the defined default state after a cold
    reset of the TPM.
  * The bit transitions to 1 only after a TPM2_Startup is issued from
    Locality 3 or 4 -- something the guest firmware/OS may or may not
    ever do, and which has not happened by the time the VMM is wiring
    up the device.

So the check was rejecting the spec-defined normal case. It also had
inverted internal naming (the boolean called "established_flag" was
true when the bit was 0), which is what made the conditional read as
if it were testing the opposite of what it actually tested.

In practice the check happened to pass on KVM and fail on MSHV (issue
socket, but the bug is independent of the backend: the VMM has no
business gating startup on tpmEstablished at all.

Drop the check. The bit is still surfaced to the guest from
Tpm::read() when CRB_LOC_STATE is read, which is the only place the
PTP spec requires it to be visible.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-01 17:46:00 +00:00
Wei Liu
ff5a6dcdb9 tpm: read swtpm control responses in full and handle short error replies
The swtpm control socket is a Unix SOCK_STREAM, so a single read() is
not guaranteed to return the full response in one shot. swtpm may
split a response into multiple writes, in which case the existing
single read() returns only the first chunk and subsequent parsing
fails with "Response for ... cmd is of incorrect length". This has
been observed on Azure Linux during emulator initialization.

In addition, when swtpm encounters an error processing a control
command (e.g. PTM_BAD_ORDINAL = 0x0A returned for commands issued
before CMD_INIT), the swtpm protocol returns only the 4-byte result
code instead of the full response. Blindly looping until msg_len_out
bytes arrive would deadlock in that case.

Add SocketDev::read_exact() that loops until the requested number of
bytes has been received (retrying on EINTR), and rework
run_control_cmd() to:

  * read_exact the 4-byte result code first;
  * on error, set the result code on the PTM message and return a
    clean error without waiting for a payload that will never arrive;
  * on success, read_exact the remaining (msg_len_out - 4) payload
    bytes.

Assisted-by: Copilot:GPT-5.5
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-06-01 17:46:00 +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
Rob Bradford
3a1cf6e740 virtio-devices: Remove panic when duplicating activate EventFds
Replace the (unlikely) panic when duplicating the EventFds with a
propagated error.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-01 17:07:23 +01:00
Dylan Reid
2bcacbe19d virtio-devices: Introduce WorkerThreads handle
Take the recent thread refactor further. Bundle a device's worker
JoinHandles together with the kill event that stops them into a single
WorkerThreads value, owned by VirtioCommon. Its Drop signals the workers
to exit, unparks any parked for migration, and joins them.

This makes a detached/leaked worker unrepresentable. reset(),
wait_for_epoll_threads() and VhostUserCommon::shutdown() now happen when
dropping the WorkerThreads, and the unpark-before-join teardown now
lives in one place.

Assisted-by: Claude:Opus-4.8
Signed-off-by: Dylan Reid <dgreid@fb.com>
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-01 17:07:23 +01:00
Rob Bradford
815a1f0801 virtio-devices: vhost_user: Use VhostUserCommon::spawn_worker
Replace manual thread management with the use of
VhostUserCommon::spawn_worker() which is just a thin layer over
VirtioCommon::spawn_worker() but handling VhostUserCommon reset. This
removes some manual thread management and also triggers a reset upon
spawn failure.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-01 17:07:23 +01:00
Rob Bradford
7538398bc5 virtio-devices: vhost_user: Add VhostUserCommon::spawn_worker helper
Wrap VirtioCommon::spawn_worker() to also include vhost-user specific
backend cleanup.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-01 17:07:23 +01:00
Rob Bradford
b5f1632177 virtio-devices: Use VirtioCommon::spawn_worker()
Replace use of spawn_virtio_thread() helper with the new method on
VirtioCommon to handle thread management as well as spawning. As a
result this cleanly handles reset if it fails to spawn the thread.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-01 17:07:23 +01:00
Rob Bradford
81e5e6d9dc virtio-devices: Add VirtioCommon::spawn_worker helper
Implement a method on VirtioCommon for spawning the worker thread. This
method also handles storing the handle to the thread and triggering a
reset on the device if thread spawning fails. The addition of this
helper replaces a repeated pattern across virtio and vhost-user devices
but also ensures correct cleanup when spawning multiple threads for a
device with multiple queues.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-01 17:07:23 +01:00
Rob Bradford
1f58e74978 virtio-devices: Simplify epoll thread handling
Rely on the vector of threads in VirtioCommon for storing all the
handles of the spawned threads rather than storing them in the devices
or in VhostUserCommon directly.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-06-01 17:07:23 +01:00
dependabot[bot]
683afbf22a build(deps): bump crate-ci/typos from 1.46.3 to 1.47.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.46.3 to 1.47.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](7b04f660f4...f8a58b6b53)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 18:38:09 +00:00
Rob Bradford
d98918d4e9 build: Put iommufd-ioctls in the rust-vmm dependabot group
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-29 17:51:00 +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
Philipp Schuster
b241084d0e vmm: migration: add vm.migration-receive-ready event
The new event allows management software to handle the migration better
via events. The `vm.migration-receive-ready` event tells that the VMM is
ready to accept connections whereas `vm.migration-receive-started` means
a migration is incoming.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-29 13:38:27 +00:00
Damian Barabonkov
bbd271e85b test_infra: improve NVIDIA diagnostics
Avoid panicking when nvidia-smi fails during GPU checks. Run the
command through a shell wrapper that preserves stdout and stderr
even when nvidia-smi exits with a failure status.

Keep printing guest dmesg on failure and label the nvidia-smi
text as diagnostic output, since SSH-level failures are reported
through the same path.

Assisted-by: OpenCode:gpt-5.5

Signed-off-by: Damian Barabonkov <dbctl@pm.me>
2026-05-29 12:20:22 +00:00
Ruben Hakobyan
99f5537984 arch: x86_64: Stop applying SME c-bit reduction to phys_bits
get_host_cpu_phys_bits() subtracts the SME c-bit reduction from
PhysAddrSize (CPUID 0x80000008 EAX bits 7:0). The result sets the
guest's CPUID and MMIO address space size.

The c-bit reduction is not needed here. QEMU's equivalent
(host_cpu_phys_bits() in target/i386/host-cpu.c) returns
PhysAddrSize without reduction.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-05-29 12:04:47 +00:00
Ruben Hakobyan
eb75a4ead7 arch: x86_64: Set GuestPhysAddrSize in CPUID leaf 0x80000008
When generating guest CPUID, we set PhysAddrSize (EAX bits 7:0)
based on the host's physical address bits. On AMD hosts with SME,
get_host_cpu_phys_bits() subtracts the c-bit reduction from this
value, but the code here only writes the result to bits 7:0 and
leaves GuestPhysAddrSize (bits 23:16) at the unmodified host value.
This creates a gap: e.g. PhysAddrSize=43 but GuestPhysAddrSize=48.

Guest firmware that reads GuestPhysAddrSize will see a larger
address space than the VMM provides, and may place PCI BARs beyond
the MMIO bus range. However, Cloud-hypervisor sizes its MMIO bus to
phys_bits.

Fix by setting both PhysAddrSize (bits 7:0) and GuestPhysAddrSize
(bits 23:16) to phys_bits, using mask 0xff00_ff00 instead of
0xffff_ff00.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-05-29 12:04:47 +00:00
dependabot[bot]
b4747dfd7f build(deps): bump the non-rust-vmm group across 2 directories with 15 updates
Bumps the non-rust-vmm group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.149` | `1.0.150` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.24` | `0.2.26` |
| [log](https://github.com/rust-lang/log) | `0.4.29` | `0.4.30` |
| [landlock](https://github.com/landlock-lsm/rust-landlock) | `0.4.4` | `0.4.5` |
| [autocfg](https://github.com/cuviper/autocfg) | `1.5.0` | `1.5.1` |
| [bumpalo](https://github.com/fitzgen/bumpalo) | `3.20.2` | `3.20.3` |
| [crypto-common](https://github.com/RustCrypto/traits) | `0.2.1` | `0.2.2` |
| [either](https://github.com/rayon-rs/either) | `1.15.0` | `1.16.0` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.98` | `0.3.99` |
| [pastey](https://github.com/as1100k/pastey) | `0.2.2` | `0.2.3` |

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

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.149` | `1.0.150` |
| [log](https://github.com/rust-lang/log) | `0.4.29` | `0.4.30` |
| [landlock](https://github.com/landlock-lsm/rust-landlock) | `0.4.4` | `0.4.5` |
| [autocfg](https://github.com/cuviper/autocfg) | `1.5.0` | `1.5.1` |
| [bumpalo](https://github.com/fitzgen/bumpalo) | `3.20.2` | `3.20.3` |
| [crypto-common](https://github.com/RustCrypto/traits) | `0.2.1` | `0.2.2` |
| [either](https://github.com/rayon-rs/either) | `1.15.0` | `1.16.0` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.98` | `0.3.99` |
| [pastey](https://github.com/as1100k/pastey) | `0.2.2` | `0.2.3` |



Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `jiff` from 0.2.24 to 0.2.26
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.24...jiff-static-0.2.26)

Updates `log` from 0.4.29 to 0.4.30
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.29...0.4.30)

Updates `landlock` from 0.4.4 to 0.4.5
- [Release notes](https://github.com/landlock-lsm/rust-landlock/releases)
- [Changelog](https://github.com/landlock-lsm/rust-landlock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/landlock-lsm/rust-landlock/compare/v0.4.4...v0.4.5)

Updates `autocfg` from 1.5.0 to 1.5.1
- [Commits](https://github.com/cuviper/autocfg/compare/1.5.0...1.5.1)

Updates `bumpalo` from 3.20.2 to 3.20.3
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.20.2...v3.20.3)

Updates `crypto-common` from 0.2.1 to 0.2.2
- [Commits](https://github.com/RustCrypto/traits/compare/crypto-common-v0.2.1...crypto-common-v0.2.2)

Updates `either` from 1.15.0 to 1.16.0
- [Commits](https://github.com/rayon-rs/either/compare/1.15.0...1.16.0)

Updates `jiff-static` from 0.2.24 to 0.2.28
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.24...jiff-static-0.2.28)

Updates `js-sys` from 0.3.98 to 0.3.99
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `pastey` from 0.2.2 to 0.2.3
- [Release notes](https://github.com/as1100k/pastey/releases)
- [Changelog](https://github.com/AS1100K/pastey/blob/master/CHANGELOG.md)
- [Commits](https://github.com/as1100k/pastey/compare/v0.2.2...v0.2.3)

Updates `wasm-bindgen` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-macro` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-macro-support` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-shared` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `log` from 0.4.29 to 0.4.30
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.29...0.4.30)

Updates `autocfg` from 1.5.0 to 1.5.1
- [Commits](https://github.com/cuviper/autocfg/compare/1.5.0...1.5.1)

Updates `bumpalo` from 3.20.2 to 3.20.3
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.20.2...v3.20.3)

Updates `crypto-common` from 0.2.1 to 0.2.2
- [Commits](https://github.com/RustCrypto/traits/compare/crypto-common-v0.2.1...crypto-common-v0.2.2)

Updates `either` from 1.15.0 to 1.16.0
- [Commits](https://github.com/rayon-rs/either/compare/1.15.0...1.16.0)

Updates `js-sys` from 0.3.98 to 0.3.99
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `landlock` from 0.4.4 to 0.4.5
- [Release notes](https://github.com/landlock-lsm/rust-landlock/releases)
- [Changelog](https://github.com/landlock-lsm/rust-landlock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/landlock-lsm/rust-landlock/compare/v0.4.4...v0.4.5)

Updates `pastey` from 0.2.2 to 0.2.3
- [Release notes](https://github.com/as1100k/pastey/releases)
- [Changelog](https://github.com/AS1100K/pastey/blob/master/CHANGELOG.md)
- [Commits](https://github.com/as1100k/pastey/compare/v0.2.2...v0.2.3)

Updates `wasm-bindgen` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-macro` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-macro-support` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-shared` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `log` from 0.4.29 to 0.4.30
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.29...0.4.30)

Updates `landlock` from 0.4.4 to 0.4.5
- [Release notes](https://github.com/landlock-lsm/rust-landlock/releases)
- [Changelog](https://github.com/landlock-lsm/rust-landlock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/landlock-lsm/rust-landlock/compare/v0.4.4...v0.4.5)

Updates `autocfg` from 1.5.0 to 1.5.1
- [Commits](https://github.com/cuviper/autocfg/compare/1.5.0...1.5.1)

Updates `bumpalo` from 3.20.2 to 3.20.3
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.20.2...v3.20.3)

Updates `crypto-common` from 0.2.1 to 0.2.2
- [Commits](https://github.com/RustCrypto/traits/compare/crypto-common-v0.2.1...crypto-common-v0.2.2)

Updates `either` from 1.15.0 to 1.16.0
- [Commits](https://github.com/rayon-rs/either/compare/1.15.0...1.16.0)

Updates `js-sys` from 0.3.98 to 0.3.99
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `pastey` from 0.2.2 to 0.2.3
- [Release notes](https://github.com/as1100k/pastey/releases)
- [Changelog](https://github.com/AS1100K/pastey/blob/master/CHANGELOG.md)
- [Commits](https://github.com/as1100k/pastey/compare/v0.2.2...v0.2.3)

Updates `wasm-bindgen` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-macro` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-macro-support` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-shared` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `log` from 0.4.29 to 0.4.30
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.29...0.4.30)

Updates `autocfg` from 1.5.0 to 1.5.1
- [Commits](https://github.com/cuviper/autocfg/compare/1.5.0...1.5.1)

Updates `bumpalo` from 3.20.2 to 3.20.3
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.20.2...v3.20.3)

Updates `crypto-common` from 0.2.1 to 0.2.2
- [Commits](https://github.com/RustCrypto/traits/compare/crypto-common-v0.2.1...crypto-common-v0.2.2)

Updates `either` from 1.15.0 to 1.16.0
- [Commits](https://github.com/rayon-rs/either/compare/1.15.0...1.16.0)

Updates `js-sys` from 0.3.98 to 0.3.99
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `landlock` from 0.4.4 to 0.4.5
- [Release notes](https://github.com/landlock-lsm/rust-landlock/releases)
- [Changelog](https://github.com/landlock-lsm/rust-landlock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/landlock-lsm/rust-landlock/compare/v0.4.4...v0.4.5)

Updates `pastey` from 0.2.2 to 0.2.3
- [Release notes](https://github.com/as1100k/pastey/releases)
- [Changelog](https://github.com/AS1100K/pastey/blob/master/CHANGELOG.md)
- [Commits](https://github.com/as1100k/pastey/compare/v0.2.2...v0.2.3)

Updates `wasm-bindgen` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-macro` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-macro-support` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

Updates `wasm-bindgen-shared` from 0.2.121 to 0.2.122
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.121...0.2.122)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: log
  dependency-version: 0.4.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: landlock
  dependency-version: 0.4.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: autocfg
  dependency-version: 1.5.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bumpalo
  dependency-version: 3.20.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: crypto-common
  dependency-version: 0.2.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: either
  dependency-version: 1.16.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.28
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.99
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: pastey
  dependency-version: 0.2.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: log
  dependency-version: 0.4.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: autocfg
  dependency-version: 1.5.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bumpalo
  dependency-version: 3.20.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: crypto-common
  dependency-version: 0.2.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: either
  dependency-version: 1.16.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.99
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: landlock
  dependency-version: 0.4.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: pastey
  dependency-version: 0.2.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: log
  dependency-version: 0.4.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: landlock
  dependency-version: 0.4.5
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: autocfg
  dependency-version: 1.5.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bumpalo
  dependency-version: 3.20.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: crypto-common
  dependency-version: 0.2.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: either
  dependency-version: 1.16.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.99
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: pastey
  dependency-version: 0.2.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: log
  dependency-version: 0.4.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: autocfg
  dependency-version: 1.5.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bumpalo
  dependency-version: 3.20.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: crypto-common
  dependency-version: 0.2.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: either
  dependency-version: 1.16.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.99
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: landlock
  dependency-version: 0.4.5
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: pastey
  dependency-version: 0.2.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.122
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 11:35:45 +00:00
dependabot[bot]
6ac614edf8 build(deps): bump actions/upload-artifact from 4 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 00:56:10 +00:00
Anatol Belski
883e3ab7fb virtio-devices: vsock: Use desc_chain.memory() for header commit
process_rx writes the packet header back into the descriptor chain
it is currently processing, so the write must go through that
chain's memory snapshot. Rederefing self.mem.memory() resolves to
the same snapshot today, but couples the write on the chain to the
device's atomic handle and obscures intent. Match the pattern used
by the rest of the device by writing through desc_chain.memory().

Suggested-by: Rob Bradford <rbradford@meta.com>
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
496ac197e6 vm-virtio: checked_descriptor: Add unit test for accessors
Drive a two descriptor chain with a writable head and a zero
length tail and verify the CheckedDescriptor accessors addr, len,
is_empty, is_write_only and has_next agree with the descriptor
flags and length on each entry.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
43ced114aa vm-virtio: checked_descriptor: Add unit test for exhausted chain
After the iterator yields the only descriptor in a chain, the
subsequent None must reflect exhaustion rather than a validation
failure, so failed and failed_addr stay unset.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
f700b10cfd vm-virtio: checked_descriptor: Add unit test for translation failure
Introduce a FailingTranslator stub whose translate_gva always
returns an error and verify CheckedDescriptorIter rejects the
descriptor, with failed_addr returning the original descriptor
address before translation.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
dd236eca90 vm-virtio: checked_descriptor: Add unit test for GVA translation
Introduce an OffsetTranslator stub implementing AccessPlatform and
verify CheckedDescriptorIter applies the translation, so the
yielded descriptor's addr reflects the translated GPA rather than
the raw descriptor address.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
e790d3b7bc vm-virtio: checked_descriptor: Add unit test for address overflow
Submit a descriptor whose addr plus len would wrap around the u64
address space and verify CheckedDescriptorIter rejects it without
panicking, with failed_addr returning the descriptor's address.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
acda9b1380 vm-virtio: checked_descriptor: Add unit test for one past memory end
Submit a descriptor whose buffer extends exactly one byte past the
end of guest RAM and verify CheckedDescriptorIter rejects it, with
failed_addr returning the descriptor's start address.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
c271a20573 vm-virtio: checked_descriptor: Add unit test for boundary descriptor
Submit a descriptor whose buffer ends exactly at the last byte of
guest RAM and verify CheckedDescriptorIter accepts it. Guards
against an off by one in the range check that would reject an
otherwise valid descriptor at the memory boundary.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
2eddaf506e vm-virtio: checked_descriptor: Add unit test for failed_addr
Drive CheckedDescriptorIter with an out of range descriptor and
assert that failed_addr returns Some carrying the GuestAddress of
the rejected descriptor, not just the failed boolean.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
356f153085 vm-virtio: checked_descriptor: Add unit test for next_checked Err
Cover the rejection path of the trait method next_checked,
asserting that an out of range descriptor is reported as
Err(addr) carrying the original descriptor address.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
3b1afe3e3d vm-virtio: checked_descriptor: Add unit test for next_checked exhaustion
Cover the exhausted path of the DescriptorChainExt::next_checked
trait method, asserting that Ok(None) is returned once the chain
has no further descriptors.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
be432bda07 vm-virtio: checked_descriptor: Add unit test for next_checked success
Cover the success path of the DescriptorChainExt::next_checked
trait method, asserting that a valid single descriptor is returned
as Ok(Some(_)) with the expected addr and len.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
d345118854 vm-virtio: checked_descriptor: Add unit test for chain stop on invalid
Add yields_valid_prefix_then_stops_on_invalid which submits a two
descriptor chain where the first descriptor is valid and the second
overshoots guest memory. The test verifies CheckedDescriptorIter
yields the valid prefix, then terminates with the failed flag set.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
3ced92702a vm-virtio: checked_descriptor: Add unit test for zero length descriptor
Add passes_through_zero_length_descriptor which submits a descriptor
with len 0 and verifies CheckedDescriptorIter yields it without
performing a guest memory range check.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
782ce21c1a vm-virtio: checked_descriptor: Add unit test for out of range descriptor
Add rejects_out_of_range_descriptor which submits a descriptor whose
length overshoots guest memory and verifies CheckedDescriptorIter
yields no descriptor and sets the failed flag.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
d24e2fd140 vm-virtio: checked_descriptor: Introduce unit_tests module
Add a unit_tests module with the first test for CheckedDescriptorIter,
covering the happy path where a valid single descriptor is yielded and
the iterator reports no failure. Wire up the virtio-bindings dev
dependency and local test helpers needed by the test.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
2bf5464f92 virtio-devices: vsock: Use checked descriptor iterator
Drop the local next_checked_desc helper and the inline
translate_gva calls in from_tx_virtq_head and from_rx_virtq_head.
Buffer ranges are now validated by the shared next_checked helper
in vm-virtio, and the validated guest address is read directly
from CheckedDescriptor::addr.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
a5962db441 virtio-devices: iommu: Validate descriptor ranges
Add check_range calls on request and status descriptor addresses to
reject buffers that extend past guest memory.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
795e606183 virtio-devices: mem: Validate descriptor ranges
Add check_range calls on request and status descriptor addresses to
reject buffers that extend past guest memory.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
661b9dee8f virtio-devices: pmem: Use checked descriptor iterator
Replace inline translate_gva and check_range with checked_iter, which
validates the descriptor buffer range against guest memory before
yielding each descriptor.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
39264eea50 virtio-devices: balloon: Use checked descriptor iterator
Replace manual translate_gva calls with checked_iter in both the
inflate/deflate and reporting queue handlers.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
33e3f4e29e virtio-devices: watchdog: Use checked descriptor iterator
Replace raw desc.addr() usage with checked_iter which validates the
descriptor buffer range against guest memory before I/O.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +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
a5c9634f7a virtio-devices: console: Use checked descriptor iterator
Replace manual translate_gva calls in both input and output queue
handlers with checked_iter for centralized range validation.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
45cba34101 virtio-devices: rng: Use checked descriptor iterator
Replace manual translate_gva call with checked_iter which validates
the descriptor buffer range against guest memory before I/O.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Anatol Belski
4c6d60f162 vm-virtio: Add centralized descriptor range validation
Introduce a CheckedDescriptorIter adapter over DescriptorChain that
validates each descriptor's translated (addr, len) range against guest
memory before yielding it. Any descriptor whose buffer is not fully
backed by guest RAM is rejected, so the device never performs I/O
against memory the guest does not actually own.

The helper lives in vm-virtio so it can be shared across the
virtio-devices and block crates, both of which already depend on
vm-virtio.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 21:41:45 +00:00
Rob Bradford
e033b593d9 scripts: Add SHA-1 checksums for kernel and static binary assets
Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-28 21:40:08 +00:00
Rob Bradford
531666bc67 scripts: Remove downloads from integration test scripts
Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-28 21:40:08 +00:00
Rob Bradford
4442d3b09b scripts: Make dev_cli.sh download assets before entering container
This will allow us to work towards removing network access from the
container.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-28 21:40:08 +00:00
Rob Bradford
23140ca013 scripts: Add asset files for all test downloads
This is structured such that if multiple test groups need the same asset
it is only listed once.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-28 21:40:08 +00:00
Rob Bradford
ff6de33e07 scripts: Add a tool for downloading test assets
This will allow us to remove the download steps from the build scripts
themselves.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-28 21:40:08 +00:00
Leander Kohler
3b79503e2f tests: cover new SMBIOS platform fields
The structured SMBIOS platform config introduced six new keys
(system_manufacturer, system_product_name, system_version,
system_family, system_sku_number, chassis_asset_tag), but
integration coverage only existed for serial_number, uuid, and
oem_strings.

Add _test_dmi_system_and_chassis, which boots a guest with all
six keys set and checks each value via `dmidecode -s` using the
same leaf name as the CLI key. Execute it in both the regular
and SEV-SNP integration suites.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-05-28 16:32:41 +00:00
Leander Kohler
b3f79e3a2f arch: smbios: add tests for table serialization
Add unit tests that walk the SMBIOS binary layout in guest memory and
verify structure ordering, string-set encoding, and error paths.

Tests added:
  - smbios_chassis_empty_string_set_has_double_null: verify that
    a chassis with no strings emits the double-NUL terminator required
    by SMBIOS DSP0134 §6.1.3.
  - smbios_chassis_oem_strings_layout: verify the full chain
    (BIOS → System → Chassis → OEM → End) when a chassis asset tag and
    OEM strings are configured.
  - smbios_strings_terminators_default: verify the default table chain
    (BIOS → System → End) and check that string indices and string-set
    contents match for both structures.
  - smbios_strings_too_many: exercise alloc_index up to the u8 limit
    (255 strings) and verify the 256th is rejected.
  - smbios_uuid_invalid_rejected: ensure a malformed UUID string is
    rejected with Error::ParseUuid.
  - smbios_uuid_written_le: ensure the UUID is stored in little-endian
    byte order as required by SMBIOS Spec 7.2.1.
  - smbios_write_fails_with_too_small_memory: verify that setup_smbios
    fails with Error::WriteData when guest memory is too small to hold
    anything beyond the entry point.

All tests also succeed when run with miri:
  cargo +nightly miri test -p arch smbios

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-05-28 16:32:41 +00:00
Leander Kohler
56439f3964 vmm: deprecate legacy SMBIOS keys in API and CLI
Mark serial_number/uuid as deprecated in the OpenAPI schema and emit
warnings when those legacy --platform keys are used, while continuing to
accept them for compatibility.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-05-28 16:32:41 +00:00
Leander Kohler
063caca4a8 vmm: platform: add structured SMBIOS config
Extend SMBIOS System Information with manufacturer, product,
version, family, sku, serial, and uuid fields, add a chassis
asset tag, and pass a structured SMBIOS config from --platform
into arch setup. Keep OEM strings and legacy serial_number/uuid
options working for compatibility. The platform option naming
follows `dmidecode -s <field>`.

Fields:
  - system_manufacturer
  - system_product_name
  - system_version
  - system_family
  - system_serial_number
  - system_uuid
  - chassis_asset_tag

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-05-28 16:32:41 +00:00
Leander Kohler
e097d7d495 vmm: plumb legacy SMBIOS config
Add a small SMBIOS config that carries serial_number, uuid,
and OEM strings, and pass it from platform config into
x86_64 setup.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-05-28 16:32:41 +00:00
Leander Kohler
0141635a5c arch: x86_64: refactor SMBIOS helpers
Split the System Information write into helper functions and
reuse the string writer so the table layout and inputs are
unchanged.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-05-28 16:32:41 +00:00
Oliver Anderson
1a8c76b0dd arch: Fix typo in generate_common_cpuid
We fix a pre-existing typo in an info event in
the `generate_common_cpuid` function.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
dbf489c1e5 arch: Apply CPU profiles
We refactor `generate_common_cpuid` to take CPU profiles into account.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
ed3a42bc9d vmm: Enable AMX states prior to checking CPUID compatibility
Since enabling AMX tile state components affect the result returned by
`Hypervisor::get_supported_cpuid` we want this enabled prior to checking
CPUID compatibility between the source and destination VMs.

Although this is not required right now, it will be necessary once we
introduce CPU profiles and it will also be necessary if we decide to
make `check_cpuid_compatibility` (arguably) more thorough by also taking
state components into account.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
39c27e1312 arch: Refactor check_cpuid_compatibility
We make a slightly more general `check_cpuid_compatibility` function
that permits the caller to specify something else than "source VM" and
"destination VM" when logging an error.

This way we can reuse the existing CPUID compatibility checks to
ensure that the host VM is compatibile with the user selected CPU
profile.

In order to avoid a "refactor the world scenario" we keep the old
function with its signature and instead refactor it to call the new
more general internal function.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
549f3d6c04 misc: Make CPU profile part of various configs
We integrate the CPU profile into the various configs that
ultimately get set by the user.

This quickly ends up involving multiple files, luckily Rust
helps us find which ones via compilation errors.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
a9440d2919 arch: adjust_cpuid method on CpuProfile
Introduce a method on `CpuProfile` that is used to adjust the given
CPUID entries according to the chosen CPU profile.

This will be used in a later commit to apply the user chosen CPU
profile.

This commit also introduces a few unit tests using relatively simple,
somewhat contrived input values.

We will introduce snapshot tests with realistic adjustments
and CPUID entries in our planned follow up PR bringing in our
pregenerated CPU profiles.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
d96de000f2 arch: CpuProfile enum
Introduce a CpuProfile enum that will be deserialized from the user's
selected CPU profile.

Currently we only have a "host" variant, but in the future there will
be a build script automatically constructing this enum based on
pre-generated CPU profiles.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
b1109f7328 arch: CpuidProfileData struct
Introduce a struct for holding CPUID adjustments related to a
CPU profile.

Instances of this struct will typically be de-serialized from JSON files
describing a CPU profile.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
e81c51c49b arch: CpuidAdjustments type
Introduce a type for adjusting CPUID entries.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
9f38cec485 arch: Serializable CPUID parameters
We introduce a type representing CPUID parameters that will be utilized
by the CPU profiles.

We place this new type in a module that will be further populated with
types related to adjusting CPUID entries based on the CPU profile
in a follow up commit.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
7700f4b585 arch: Helper functions for u32 hex (de-) serialization
These helper functions will later be used to (de-) serialize CPUID
leaves and MSR register addresses.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
110c295edf arch: Add proptest as a dev-dependency
Not all invariants can be effectively enforced by Rust's type system
and when this is the case we typically want tests that assert that the
promised invariants do indeed hold.

To get good confidence that our invariants do indeed hold we want to
check against many inputs, but having to write down many inputs is
tedious, and we may also be "biased" in our choices.

Property based testing helps here as it provides several randomly
generated inputs for us and we can focus on just writing the
test logic.

We thus add the popular user friendly `proptest` library as a
dev-dependency in order to write property based tests in follow up
commits. We emphasize that this will be particularly important in
a follow up PR where we have some relatively complex logic for
filtering MSRs.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
64545eb7dc arch: Introduce common traits for CpuidReg
Also derive Eq, Serialize and Deserialize for CpuidReg. This will make
it possible to reuse this existing type in the context of CPU profiles.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +00:00
Oliver Anderson
5cb619ff7c hypervisor: Implement common traits for CpuVendor
Serialized CPU profiles will contain information about the CPU vendor.
While there are other ways to encode this, such as going via CPUID, we
find simply serializing the pre-existing enum the simplest.

We also implement some other common traits such as `Debug` and `Eq`
which make it more convenient to work with this type.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
2026-05-28 15:19:15 +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
Anatol Belski
f8327faa33 block: Remove raw_disk compat alias
Update external consumers and internal test modules to use
formats::raw instead of the raw_disk alias, then remove the
re-export from lib.rs.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
25afb8898c block: Move QCOW2 format files into formats/qcow/
Move QCOW2 format implementation into a structured directory layout:

  qcow/              -> formats/qcow/internal/  (filenames unchanged)
  qcow_disk.rs       -> formats/qcow/mod.rs               (QcowDisk)
  qcow_sync.rs       -> formats/qcow/worker/sync.rs       (QcowSync)
  qcow_async.rs      -> formats/qcow/worker/async_uring.rs (QcowAsync)
  qcow_common.rs     -> formats/qcow/common.rs

All internal cross references continue to resolve through
re-exports in lib.rs: formats::qcow::internal as qcow,
formats::qcow as qcow_disk, and
formats::qcow::common as qcow_common.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
b595f1dbc3 block: Move VHDX format files into formats/vhdx/
Move VHDX format implementation into a structured directory layout:

  vhdx/mod.rs        -> formats/vhdx/internal/mod.rs      (Vhdx)
  vhdx/vhdx_bat.rs   -> formats/vhdx/internal/bat.rs
  vhdx/vhdx_header.rs -> formats/vhdx/internal/header.rs
  vhdx/vhdx_io.rs    -> formats/vhdx/internal/io.rs
  vhdx/vhdx_metadata.rs -> formats/vhdx/internal/metadata.rs
  vhdx_sync.rs       -> formats/vhdx/mod.rs               (VhdxDisk)

Extract VhdxSync from vhdx_sync.rs into formats/vhdx/worker/sync.rs.
Drop the vhdx_ prefix from internal file names since the parent
directory already provides the namespace. Update all internal cross
references to use the new module paths. Re-export formats::vhdx as
vhdx_sync in lib.rs for backward compatibility.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
1ca0c39b4d block: Move VHD format files into formats/vhd/
Move VHD format implementation into a structured directory layout:

  fixed_vhd.rs       -> formats/vhd/internal/fixed.rs     (FixedVhd)
  fixed_vhd_disk.rs  -> formats/vhd/mod.rs                (VhdDisk)
  vhd.rs             -> formats/vhd/internal/footer.rs     (VhdFooter)
  fixed_vhd_sync.rs  -> formats/vhd/worker/sync.rs         (FixedVhdSync)
  fixed_vhd_async.rs -> formats/vhd/worker/async_uring.rs  (FixedVhdAsync)

Add #[allow(dead_code)] to VhdFooter struct and impl because the
module is now pub(crate) and the compiler can see that several
fields and getters are only exercised by unit tests. Re-export
formats::vhd as fixed_vhd_disk in lib.rs for backward
compatibility with external consumers.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
a11f551572 block: Move raw format files into formats/raw/
Move raw format implementation into a structured directory layout:

  raw_disk.rs        -> formats/raw/mod.rs                (RawDisk)
  raw_sync.rs        -> formats/raw/worker/sync.rs        (RawSync)
  raw_async.rs       -> formats/raw/worker/async_uring.rs (RawAsync)
  raw_async_aio.rs   -> formats/raw/worker/async_aio.rs   (RawAio)
  raw_async_io_tests.rs -> formats/raw/worker/tests.rs

Update imports in fixed_vhd_sync.rs and fixed_vhd_async.rs to use
the new paths. Re-export formats::raw as raw_disk in lib.rs to
preserve the external API.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
b68349f8b3 block: Create io/ module for shared I/O infrastructure
Move async_io.rs, fcntl.rs, and request.rs into block/src/io/. These
files are generic I/O infrastructure shared by all formats rather
than format specific code.

The io/ directory name clashes with std::io in lib.rs, so the module
is declared as io_impl via #[path] and the submodules are re-exported
at the crate root to keep existing import paths working.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
2689cf9bdb block: vhd: Rename FixedVhdDisk to VhdDisk
FixedVhdDisk is the format level DiskFile wrapper for VHD images.
Rename it to VhdDisk to match the <Format>Disk convention used by
RawDisk, QcowDisk, and VhdxDisk. The Fixed prefix remains on the
workers (FixedVhdSync, FixedVhdAsync) since those are specific to
the fixed subformat.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
82e1c08e1f block: vhdx: Rename VhdxDiskSync to VhdxDisk
Align with the <Format>Disk wrapper naming convention. VHDx
has a single on disk format so no variant suffix is needed.
The async backend will be added to VhdxDisk.

No functional change.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
45fd51652f block: raw: Rename RawFileAsyncAio to RawAio
Apply the consistent <Format><Backend> naming convention.

No functional change.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
ac3c53cebb block: raw: Rename RawFileAsync to RawAsync
Apply the consistent <Format><Backend> naming convention.

No functional change.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
Anatol Belski
6279214dff block: raw: Rename RawFileSync to RawSync
Apply the consistent <Format><Backend> naming convention.

No functional change.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-28 11:58:58 +00:00
dependabot[bot]
8835656f21 build(deps): bump actions/download-artifact from 4 to 8
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 01:42:32 +00:00
Henry Hrvoje Tonkovac
b415fbf5ac arch: trim qualified paths in tdx
Import std::io and std::mem instead of spelling the full paths at every
use site.

Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com>
Assisted-by: Claude:Opus-4.7
2026-05-27 23:59:56 +01:00
Rob Bradford
003e878344 build: Parallelise docker container build
Build both targets in parallel and then recombine them into a multiarch
digest later. This matches the official docker/build-push-action
multi-platform pattern and preserves the existing tagging behaviour.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 20:29:46 +00:00
Rob Bradford
2545a6f8b5 tests: Remove building of virtiofsd from the test scripts
It is now provided by the container.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 20:29:46 +00:00
Rob Bradford
1bce33923b build: Bump container version
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 20:29:46 +00:00
Rob Bradford
98356bc683 build: Dockerfile: Bump the version of spdk used
This one no longer requires pip as all the dependencies should be
included in modern Ubuntu.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 20:29:46 +00:00
Rob Bradford
a73779dad9 build: Dockerfile: Build virtiofsd
Use the same hash that we have been testing with when building from
within the integration tests.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 20:29:46 +00:00
Rob Bradford
66441e763e build: Dockerfile: Cleanup cargo registry at the end
Cleaning up the cargo registry so that it can be used in the container
should be done towards the end of the Dockerfile allowing more Rust
tools be to used.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 20:29:46 +00:00
Rob Bradford
29f392f8d4 tests: Fix clippy: uninlined_format_args
Replace format arguments with inlined versions.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
eca621c4e6 tests: Fix clippy: useless_borrows_in_formatting
Replace use of redundant & (leading to &&) in format strings.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
4d27640d08 vmm: Fix clippy: uninlined_format_args
Inline the format argument rather than provide it as a parameter.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
85415cd05b test_infra: Fix clippy: uninlined_format_args
Inline the format argument rather than provide it as a parameter.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
ead1094629 arch: Fix clippy: unused_format_specs
When formatting as hex the minimum format size is 4 not 2.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
7a386eca4a vmm: Fix clippy: useless_borrows_in_formatting
Replace & in formatting arguments where it is already a reference
(avoiding &&).

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
c93677c426 virtio-devices: Fix clippy: useless_borrows_in_formatting
Replace & in formatting arguments where it is already a reference
(avoiding &&).

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
c10f237529 test_infra: Fix clippy: useless_borrows_in_formatting
Replace & in formatting arguments where it is already a reference
(avoiding &&).

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
2a20f570b2 vmm: Fix clippy: for_kv_map
Fix use of a paired map iterator when only the values are cared about.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
c78497c314 virtio-devices: Fix clippy: for_kv_map
Fix use of a paired map iterator when only the values are cared about.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Rob Bradford
673d4a441c hypervisor: Fix clippy: for_kv_map
Fix use of a paired map iterator when only the values are cared about.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-27 16:50:41 +00:00
Anatol Belski
d0634d18d9 virtio-devices: Test PCI CFG data_len as access bound
Verify that bar_access_params uses data_len when it is smaller than
cap.length.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-27 13:36:15 +00:00
Anatol Belski
28aa81e66b virtio-devices: Test PCI CFG access length clamping
Verify that bar_access_params clamps the access length to cap.length
when the PCI config read buffer is larger.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-27 13:36:15 +00:00
Anatol Belski
feb1c4a2d6 virtio-devices: Respect PCI CFG cap.length for BAR access
The VIRTIO_PCI_CAP_PCI_CFG indirect access mechanism was ignoring
the cap.length field written by the guest driver. PCI config register
reads always produce a 4 byte buffer, so when a driver set cap.length
to 1 for a byte wide access to device_status at common config offset
0x14, the VMM passed all 4 bytes to read_bar, dispatching to the
dword handler which does not cover that offset.

Use cap.length to determine the actual BAR access width per virtio
spec 4.1.4.9.1. Also replace the unsafe transmute with the safe
Le32::to_native() conversion.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-27 13:36:15 +00:00
Dylan Reid
cc2e528c88 block: Avoid raw iovecs in VHDX sync I/O
The VHDX synchronous async I/O backend still converted owned
AsyncIoOperation targets back into raw iovec slices before calling the
VHDX read and write helpers. That kept pointer dereferences in the owned
path and allowed a backend mistake to violate the safety boundary.

Handle owned VHDX reads and writes through AsyncIoOperation copy helpers
instead. The VHDX file operations still run synchronously, but data is
copied through operation-owned buffers or guest-memory targets without
reconstructing Rust slices from raw iovec pointers.

Assisted-by: Codex:GPT-5
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
92d9a53ed3 block: Avoid raw iovecs in qcow sync I/O
This removes the qcow sync raw-iovec unsafe path and drops the
now-unused qcow iovec scatter/gather helpers.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
892bc16806 block: Avoid raw iovec access in qcow async fallbacks
Similar to the other functions fixed in this series, qcow has helpers
that dereference whatever pointers are passed but are labeled safe.
Use the newly added ops helpers to, instead, provide a safe interface
and implementation.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
1b2326fde4 block: Remove legacy async I/O API
Drop the borrowed iovec AsyncIo entry points now that all callers use
owned operations. Rename the transitional owned batch and completion
methods to the final trait names and remove the borrowed submission
helpers from the queue wrappers.

This removes a bunch of known safety foot-guns so future-us don't
accidentally use them.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
2da8507d21 block, virtio-devices: Use owned async I/O requests
Switch virtio-blk request construction and the users to the owned
AsyncIo data path added in the series. Read bounce buffers now return
through AsyncIoCompletion before being copied back to guest memory.

This makes the main virtio async block I/O path use retained request
memory. qcow still has raw-iovec fallback paths at this point; those
are removed in follow-up commits.

Leave the legacy borrowed iovec trait methods in place for a follow-up
cleanup commit to minimize single-commit churn.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
1dfc642e9a performance-metrics: Use owned async block I/O
Switch the in-process block microbenchmarks to allocate prefaulted
GuestMemory regions and submit through the memory-target AsyncIo API.

This adds a few setup steps as the existing benchmarks relied on the
unsound iovec API. The new behavior is intended to be as close as
possible to the existing tests and the common path for running
cloud-hypervisor.

Assisted-by: Codex:GPT-5.5
Assisted-by: Claude:Opus-4.7
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
358f7671ff block: Add owned async request helpers
Add helper routines for building and transferring data to/from async
io requests.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
afdc87e971 block: Add owned qcow io_uring I/O path
Similar to the previous commits, use UringDataIo for qcow async. Again,
the legacy interfaces are kept(at the expense of some temporary code).
The temporary code is unsound, like the existing code, but will be
removed soon.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
9143039805 block: Add owned raw Linux AIO path
Start using AioDataIo from RawFileAsyncAio. This adds a temporary
submit_borrowed_operation to enable preserving the unsafe iovec api
until we can remove it in the forthcoming commits.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
a541baa9e4 block: Add owned fixed VHD io_uring I/O path
Add the new AsyncIo apis to fixed_vhd_async. Later commits update
callers to use them and remove their unsound counterparts that take
iovecs.

While doing this, make the owned path validate offset plus length
instead of only the starting offset, so requests that extend past the
VHD size are rejected.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
849dace891 block: Add owned raw io_uring I/O path
Route RawFileAsync data I/O through UringDataIo's owned-operation
retention path while keeping borrowed submissions available for the
legacy AsyncIo calls during the transition.

This mostly moves code around, temporarily moving uring handling from
RawFileAsync to the UringDataIo, including the unsafe iovec access.

This enables the UringIo to be added to RawFileAsync incrementally.
Later commits will remove the unsafe paths when the callers are updated
to use the new functions.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
6a0c0191c8 block: Add owned qcow sync I/O path
Implement the new AsyncIo members for qcow_sync while keeping the
legacy borrowed iovec methods in place.

The code before and after this commit is equally unsound. This
intermediate state is not a safety regression and allows for a
bisectable transition to the fully sound code at the end of the
series.

This temporary state breaks out the iovec accesses to helpers used
from both the old and new code and updates the safety comments to
reflect reality.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
491f4e3343 block: Add owned VHDX sync I/O path
Refactor VhdxSync around shared iovec helpers which are marked unsafe.
Use these to implement safe wrappers for the new AsyncIo trait.

Leave the existing, unsound read/write vectored calls in place until
all callers are converted to the new interface later in this series.

In addition VHDX code assume it's safe to create slices to GuestMemory
via the AsyncAdaptor in existing code and explicitly after this
change. This is technically unsound as it can easily create multiple
mut refs. At least this is 'llvm update breaks the code' UB, not guest
exploitable UB...

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
0bb4b87bcd block: Add owned fixed VHD sync I/O path
Implement the safe AsyncIo interface the fixed VHD synchronous wrapper
and delegate the actual I/O through RawFileSync. This maintains the
existing interfaces until the callers are converted later in the series.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
68b9ddd01c block: Add owned raw sync I/O path
Implement the owned AsyncIo path for RawFileSync while keeping the
legacy borrowed iovec methods available until all callers can be
converted.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
83a294505e block: Add owned AsyncIo trait methods
Add owned data-operation, completion, and batch methods to AsyncIo.
These will be used as safe alternatives to the existing, comically
unsafe, but marked safe interface.

Over the course of the following commits, users are converted to the
new interface and after all users of the unsound interfaces are
removed, they are removed.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
ae5f4664ac block: Add AIO async struct
Similar to uring io added in the parent commit. These async ops deal
with buffer ownership across aio calls.

This will be used in the (increasingly rare) case of io_uring not being
available or desirable.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
b9ec794719 block: Add io_uring async I/O struct
Add the shared helpers and UringDataIo queue that drive uring async I/O
operations. UringDataIo is the key component responsible for keeping the
memory pointers active while async operations are ongoing. It uses the
async core added in previous commits.

Later commits will change the block backends to use this instead of the
lower level abstractions directly.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
dd1fb36f36 block: Add owned async I/O operations
Add `AsyncIoOperation` and `AsyncIoCompletion` as the owned request and
completion types that will be used to ensure buffers for async io
outlive the operations that use them. Later commits will update the
`AsyncIo` trait to expose apis using only these instead of raw iovecs.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
5bf029f3b7 block: Add owned async I/O buffers
Add `OwnedIoBuffer` to be used for host owned buffers. These are buffers
backed with either a `Vec` or an aligned allocation and will be used for
bounce buffers. This is host owned memory that can later be copied to
guest memory.

Later commits will use this to ensure backing memory outlives async
operations in a centralized, verified way.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Dylan Reid
110487a55b block: Add guest-memory async I/O targets
Introduce GuestMemoryTarget to own the combination of an Arc to
GuestMemory and a set of ranges/iovecs. This will be used in the
following commits to replace the iovec pointers that are passed to the
backend operations unsafely.

Assisted-by: Codex:GPT-5
Assisted-by: Claude:Opus-4.7
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-27 12:07:03 +00:00
Roman
a1a84477cf vmm: openapi: Fix integer schema formats
The current OpenAPI schema format is wrong because uint16 and uint32 are
not valid top-level OpenAPI types. Describe queue_size and virtio_id as
integer types with explicit uint formats instead.

Signed-off-by: Roman <roman@vanesyan.com>
2026-05-27 09:08:23 +00:00
Cameron Baird
f73eb3ef91 ci: Add integration test for virtio-rtc
Integration test for virtio-devices/src/rtc.rs. Requires that
the test kernel has the following configs:

CONFIG_PTP_1588_CLOCK=y
CONFIG_VIRTIO_RTC=y
CONFIG_VIRTIO_RTC_PTP=y

Signed-off-by: Cameron Baird <cameronbaird@microsoft.com>
2026-05-26 20:00:24 +00:00
Cameron Baird
b452440f6c virtio-devices: Implement virtio-device rtc
This change will allow us to get accurate time over ptp in guests
started from a MSHV-virtualized Linux host. Implementing it as a
virtio device is preferable to using the existing kvm_ptp because:

kvm_ptp relies on hypercalls that only exist on host kernels running
kvm. Virtio-rtc gives us more flexibility in what clock types we want
to provide. We can later extend the device to implement multiple clocks
(smeared UTC, TAI, monotonic, etc.). Virtio-rtc protocol supports
alarms. Alarms may later enable usecases where the guests can do their
own VM lifecycle management without relying on a host-side
orchestrator.

Implement device backend for virtio-rtc. Currently this implementation
encompasses:

1. CONFIG, CAP, READ, CROSSCAP (returns false)
2. One PTP clock is presented of type
VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED with leap_second_smearing
VIRTIO_RTC_SMEAR_UNSPECIFIED

The device is disabled by default, requiring --rtc to be passed

Not implemented but theoretically supported by virtio-rtc is:

1. Cross-timestamping support
2. The alarm queue

Fixes #7730

Signed-off-by: Cameron Baird <cameronbaird@microsoft.com>
2026-05-26 20:00:24 +00:00
wangyf0611
1e18716fbd arch, vmm: Fix riscv64 build gaps
Several riscv64 paths are compiled by the KVM build but missed
imports or cfg coverage needed by the current code.

Import the vm-memory Bytes trait for the RISC-V UEFI loader, keep
Instant available for migration timing code, and enable the UEFI flash
error path for riscv64.

Assisted-by: OpenAI-Codex:GPT-5

Signed-off-by: wangyf0611 <wangyufeng@iscas.ac.cn>
2026-05-26 15:23:47 +00:00
wangyf0611
7d1321515c vmm: Allow riscv64 path syscalls in seccomp filters
riscv64 does not provide the legacy readlink and unlink syscalls, so
libc uses readlinkat and unlinkat for paths that are otherwise allowed
on other architectures.

Permit readlinkat and unlinkat for the VMM and vCPU seccomp filters on
riscv64, matching the existing aarch64 rules.

Assisted-by: OpenAI-Codex:GPT-5

Signed-off-by: wangyf0611 <wangyufeng@iscas.ac.cn>
2026-05-26 15:23:47 +00:00
wangyf0611
649ca23345 arch, hypervisor: Report KVM IMSIC interrupt IDs
The RISC-V AIA FDT node currently advertises a fixed riscv,num-ids
value. That can diverge from the interrupt identity count configured by
KVM, which matters for guests running with an emulated IMSIC.

Record the NR_IDS value reported by KVM and expose that value through
the generated device tree. Read back the KVM-selected AIA mode without
forcing an emulation mode.

Assisted-by: OpenAI-Codex:GPT-5

Signed-off-by: wangyf0611 <wangyufeng@iscas.ac.cn>
2026-05-26 15:23:47 +00:00
wangyf0611
9c2e2a67cc hypervisor: Enable sstateen0 for KVM vCPUs
KVM initializes RISC-V vCPUs with sstateen0 cleared. When AIA is
exposed to the guest, Linux touches supervisor AIA CSRs while bringing
up the IMSIC path, and those accesses fail if the stateen bits remain
disabled.

Program sstateen0 for newly created vCPUs so the guest can use the
supervisor interrupt state needed by AIA.

Assisted-by: OpenAI-Codex:GPT-5

Signed-off-by: wangyf0611 <wangyufeng@iscas.ac.cn>
2026-05-26 15:23:47 +00:00
dependabot[bot]
5596f0aec1 build(deps): bump crate-ci/typos from 1.46.2 to 1.46.3
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.46.2 to 1.46.3.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](aca895bf05...7b04f660f4)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.46.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-26 06:04:02 +00:00
Bo Chen
8fd8224ee3 vmm: Use HashSet for VmConfig::preserved_fds
Each VM reboot re-entered the VFIO/virtio-net add paths and re-appended
the same originating fds, leaving duplicates in preserved_fds and a
double-close hazard at final teardown. Switching to HashSet makes
add_preserved_fds idempotent.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
2026-05-23 07:29:10 +00:00
Ruben Hakobyan
5c25d82f59 hypervisor, vmm: Fix aarch64 SVE register save/restore
When SVE is enabled, KVM replaces the FPSIMD V-registers with wider
SVE Z-registers. Attempting to access the old FPSIMD offsets returns
EINVAL.

Fix by classifying each register from KVM_GET_REG_LIST as core, system,
or extended. Extended registers (currently SVE only) are saved as
generic `ExtendedReg` entries split into `pre_finalize_regs` (registers
like SVE VLS that must be written before `vcpu_finalize`) and
`extended_regs`. FPSIMD registers are only accessed when SVE is absent.
Unrecognized register families error immediately so future extensions
like SME fail clearly rather than silently losing state.

The snapshot is deserialized before vCPU init to make pre-finalize
register state available for the init -> VLS -> finalize ordering
required by KVM.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-05-22 20:03:06 +00:00
Ruben Hakobyan
e390f0bdc1 vmm: Extract SVE finalization from vCPU init
Split the SVE detection and finalization logic out of `init()` into a
dedicated `finalize_sve()` method. This separates the concerns of vCPU
initialization (preferred target, processor features, KVM init) from SVE
finalization, and enables a subsequent commit to insert SVE VLS register
in the restore path without needing to add a restore state in `init()`.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-05-22 20:03:06 +00:00
Ruben Hakobyan
177d8b80e2 hypervisor: Extract FPSIMD register access into helper methods
Move the inline FPSIMD register read/write code from `get_regs()` and
`set_regs()` into dedicated `get_fpsimd_regs()` and `set_fpsimd_regs()`
methods on `KvmVcpu`.

This helps keep the larger `{get,set}_regs()` easier to understand and
prepares them for a subsequent commit that needs to conditionally
skip FPSIMD access when SVE registers are used instead.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-05-22 20:03:06 +00:00
Ian Klemm
2b61bda35a block: qcow: check compressed L2 entries before zero flag
QCOW2 compressed L2 entries encode their extent layout in bits that
overlap with the flags used by standard L2 entries. In particular,
bit 0 can be part of the compressed entry layout, so it must not be
interpreted as ZERO_FLAG until the entry has first been ruled out as
compressed.

Keep compressed deallocation ahead of zero-flag handling in both the
shared QcowMetadata path and the legacy QcowFile path. This ensures
WRITE_ZEROES deallocates compressed clusters instead of treating a
compressed entry with bit 0 set as an existing logical-zero marker.

Add regression coverage that forces bit 0 on a compressed L2 entry
and verifies WRITE_ZEROES still clears the entry through the
compressed-cluster path.

Assisted-by: Codex:GPT-5
Signed-off-by: Ian Klemm <hi@ianklemm.de>
2026-05-22 14:26:04 +00:00
Ian Klemm
39e253ff9c block: qcow: Preserve WRITE_ZEROES with backing files
QCOW2 empty L2 entries in an overlay mean that reads fall
through to the backing file. Reusing the punch_hole path for
WRITE_ZEROES therefore turns a full-cluster zero operation on an
unallocated overlay cluster into backing data exposure.

Keep discard/punch_hole behavior unchanged, but let WRITE_ZEROES
request a logical-zero marker when the image has a backing file.
ZERO_FLAG entries now read as zeros in both the legacy QcowFile
path and the shared runtime metadata path. Partial writes after
such entries seed new clusters from zeros instead of backing data.

Treat ZERO_FLAG entries as logical holes for SEEK_HOLE/SEEK_DATA.
Empty overlay entries with a backing file still report data because
the data exists in the backing file.

Avoid cluster-sized userspace zero buffers when materializing
zero-flagged clusters by zeroing the allocated host range directly.
This keeps recycled clusters safe without making partial writes
allocate large zero-filled Vecs.

Add regression coverage for legacy QcowFile, QcowSync, direct I/O,
QcowAsync/io_uring overlay paths, and a large-cluster partial-write
case.

Assisted-by: Codex:GPT-5

Signed-off-by: Ian Klemm <hi@ianklemm.de>
2026-05-22 14:26:04 +00:00
dependabot[bot]
4992d4bf7d build(deps): bump the non-rust-vmm group across 2 directories with 2 updates
Bumps the non-rust-vmm group with 1 update in the / directory: [openssl-sys](https://github.com/rust-openssl/rust-openssl).
Bumps the non-rust-vmm group with 1 update in the /fuzz directory: [winnow](https://github.com/winnow-rs/winnow).


Updates `openssl-sys` from 0.9.115 to 0.9.116
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.115...openssl-sys-v0.9.116)

Updates `winnow` from 1.0.2 to 1.0.3
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v1.0.2...v1.0.3)

---
updated-dependencies:
- dependency-name: openssl-sys
  dependency-version: 0.9.116
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: winnow
  dependency-version: 1.0.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-22 00:48:04 +00:00
Rob Bradford
e0c68dbfaa main: Add support for "glog style" timestamps
Add support for formatting logs following the popular glog crate style.
In particular this can use local or UTC time along with a single
character level.

As an implementation detail of that implement finegrained date time log
outputs including both local and UTC variants.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:Opus-4.6
2026-05-21 22:38:15 +00:00
Philipp Schuster
1924153185 tests: cover restored VM disk hotplug after TCP migration
Extend the TCP live-migration test with a hotplugged block device using
a stable ID before migration. After migration, verify that the disk
still exists on the restored destination VM.

Then hot-remove the disk and add it again with the same ID. This covers
the stale restore snapshot case because the disk ID exists in the
migration snapshot, but the live device tree no longer contains it after
hot-remove.

Assisted-by: Codex:GPT-5.5
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-21 15:49:51 +00:00
Rob Bradford
44ed81e66f vmm: Don't store the snapshot on the DeviceManager
Storing the snapshot causes issues when needing to do a subsequent
hotplug instead just pass it through on all the methods that need it
making the lifecycle cleaner.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-20 11:10:39 +00:00
Wei Liu
fe57bb8846 docs: do not discourage agents from running integration tests
Agents are totally capable of setting up the host and run dev_cli.sh.
The removed text discouraged them to do that even when asked to.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-05-19 23:11:54 +00:00
Julian Schindel
0ee3349889 net_util: remove MAX_INTERFACE_NAME_LEN
`MAX_INTERFACE_NAME_LEN` is equivalent to `libc::IFNAMSIZ`, so we use
that instead of maintaining our own const.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
2026-05-19 20:24:35 +00:00
Wei Liu
3837c87f1f vmm: add seccomp filter for serial-manager thread
The serial-manager thread was the only VMM-managed thread without a
seccomp filter. Add a Thread::SerialManager variant and whitelist the
31 syscalls needed for its epoll-based I/O loop (read, write, socket
ops, signal handling, memory allocation, glibc internals).

The filter is computed in start_thread() and applied before the epoll
loop, matching the pattern used by other VMM threads.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Assisted-by: Pi-agent:Claude-Opus-4.7
2026-05-19 16:57:53 +00:00
Wei Liu
73146be06b vmm: simplify seccomp code
Only Thread::Vmm and Thread::Vcpu need to know the hypervisor type.
Make the type optional, and then simplify the users.

Assisted-by: Pi-agent:Claude-Opus-4.7
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-05-19 16:57:53 +00:00
Rowen-Ye
59b72c51c2 block: Resolve relative QCOW2 backing paths
QCOW2 backing file paths stored in image headers may be
relative. These paths should be interpreted relative to the image
that references them, but the block backend opened them relative to
the process working directory.

Resolve the current image path inside parse_qcow() from the open
file descriptor and use its parent directory for relative backing
paths. Recursive backing chains work the same way because each layer
is parsed from its own file descriptor.

Signed-off-by: Rowen-Ye <rowenye1@gmail.com>
2026-05-19 15:05:56 +00:00
Sebastien Boeuf
6ef9b58615 vmm: Prefault snapshot pages in background
Userfaultfd is a great mechanism for providing fast restore to Cloud
Hypervisor VMs. But that means the price to pay for bringing pages in
happens at runtime, which might slow down the guest when it's touching
pages which haven't been brought in yet.

By prefaulting the pages in the background, we're trying to get the best
of both worlds. That means we still get a very fast restore with the
uffd handler, but within a few seconds (depending on VM's RAM size), we
also get the pages fully faulted and we can stop the uffd handler thread
at that point.

Signed-off-by: Sebastien Boeuf <sboeuf@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-19 12:54:55 +00:00
dependabot[bot]
0e7a42c9e7 build(deps): bump crate-ci/typos from 1.46.1 to 1.46.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.46.1 to 1.46.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](5374cbf686...aca895bf05)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.46.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-19 10:56:40 +00:00
Anirudh Rayabharam
fcd69fb707 tests: consolidate test config into nextest profiles
Centralize test configuration (filters, retries, fail-fast,
sequential/parallel scheduling) into nextest profiles, replacing
scattered flags across shell scripts. This simplifies the scripts
and provides a single source of truth for test behavior.

Enable JUnit XML output per profile, giving CI systems structured
test results for better reporting.

Not all test invocations are converted to profiles yet. Just the
ones that are repeated across scripts.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-05-19 09:09:55 +00:00
Anirudh Rayabharam
a4f0a18fb5 ci: update Docker image to use nextest 0.9.128
Update the container image tag to trigger a rebuild. The new image
pins cargo-nextest to version 0.9.128, which supports profile
inheritance, needed for the following nextest configuration changes.

Assisted-by: Copilot-CLI:Claude-Opus-4.7
Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-05-19 09:09:55 +00:00
tchaton
5080d03714 vmm: openapi: Fix typo in iommu_address_width field name
The JSON parser expects the field to be named
`iommu_address_width_bits`, but the code declared it as
`iommu_address_width`. This mismatch caused the field to be
unrecognized when deserializing configuration from JSON.

Rename the field to `iommu_address_width_bits` to match the
expected schema.

Signed-off-by: tchaton <thomas.chaton.ai@gmail.com>
2026-05-19 08:30:16 +00:00
CMGS
f8f92bd628 virtio-devices: 8 MiB-aligned initial BAR placement
Windows 11 PnP rebalance rewrites peer BARs into the same range CH
packed the initial layout at, causing move_bar() failures and boot
deadlock. Pack Mmio64 BARs at 8 MiB stride. Mmio32 isn't wide enough
for the same stride, but its BARs don't participate in guest BAR
rebalancing.

On restore, pin the BAR to the snapshot address (alignment=None) so a
guest-relocated BAR with smaller alignment is accepted.

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-05-19 08:19:25 +00:00
Dylan Reid
a495841515 virtio-devices: iommu: log per-request errors
IommuEpollHandler::request_queue() can fail because the guest put in a
bad request or because of a fatal error. Handle those cases differently,
letting the guest continue, but see the error if it can.

This makes debugging from the guest easier as one mistake doesn't cause
a VM reset if it's avoidable.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-19 08:16:33 +00:00
Dylan Reid
ab38a77c01 pci: vfio_user: replace unwrap() with explicit error
VfioUserDmaMapping::map panicked when find_region returned an anonymous
mmap region. Change this so a user gets an error instead of a panic.

When the VMM hotplugs a region into a guest that also has a vfio-user
device, all region's handlers are called. With the anonymous memory
backing (no file=, shared=on, or hugepages), region.file_offset()
returns None and the .unwrap() panics the VMM. Replace the unwrap with
an explicit error and use checked_add for the offset combine.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-19 08:15:54 +00:00
Philipp Schuster
a917f5208a docs: be more explicit in AGENTS.md
Otherwise, codex often says "Codex:GPT-5" which is rather unspecific.
"Codex:GPT-5.4" would be better.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-18 21:09:25 +00:00
Philipp Schuster
75baa46816 docs: Mention EditorConfig in agent guide
Tell agents to respect .editorconfig alongside the formatter guidance
from CONTRIBUTING.md.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-18 21:09:25 +00:00
Philipp Schuster
466b5a8e08 docs: Move logging guidance to CONTRIBUTING
Keep logging and comment-style guidance in CONTRIBUTING.md and trim
duplicate policy from the agent guide.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-18 21:09:25 +00:00
Philipp Schuster
a07097af1f docs: Document supported targets
Move supported architecture and backend guidance from AGENTS.md into
README.md and CONTRIBUTING.md.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-18 21:09:25 +00:00
Wei Liu
8ff6114c46 build: make Windows tests blocking again
See #8211. The bug is with a change in memory allocation behaviour, not
with Windows guests.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-05-18 21:05:37 +00:00
Anatol Belski
2c86362674 virtio-devices: Test cap_len of sibling compound virtio PCI caps
Assert VirtioPciNotifyCap and VirtioPciCap64 size cap_len from
their own type. Catches a future regression of the same shape as
the VirtioPciCfgCap one in any of the sibling capabilities.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-18 14:30:46 +00:00
Anatol Belski
b776c6d317 virtio-devices: Test cfg_type of VIRTIO_PCI_CAP_PCI_CFG
Assert the emitted VirtioPciCfgCap carries cfg_type 5, the value
assigned to PciCapabilityType::Pci by virtio 1.2 section 4.1.4.1.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-18 14:30:46 +00:00
Anatol Belski
e7d394e286 virtio-devices: Test cap_len of VIRTIO_PCI_CAP_PCI_CFG
Regression test for the cap_len fix. The emitted VirtioPciCfgCap
must report cap_len 20, covering the trailing pci_cfg_data window
per virtio 1.2 section 4.1.4.9.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-18 14:30:46 +00:00
Anatol Belski
b4dea599a3 virtio-devices: Fix cap_len for VIRTIO_PCI_CAP_PCI_CFG
VirtioPciCfgCap::new built its inner header via VirtioPciCap::new,
which sized cap_len from the bare virtio_pci_cap layout, yielding
16. The emitted capability is VirtioPciCfgCap, which appends a four
byte pci_cfg_data window, so the correct value is 20.

The virtio 1.2 specification defines this cap as virtio_pci_cap
followed by pci_cfg_data[4] and requires cap_len to
cover the whole structure. Build the header inline so cap_len
reflects the actual emitted size, matching VirtioPciNotifyCap and
VirtioPciCap64.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-18 14:30:46 +00:00
Rob Bradford
547a78999e vmm: Revert "vmm: create memfd for private mappings"
This reverts commit ced3762a67.

This change lead to a serious memory regression when not using hugepages
or shared=on.

`MAP_PRIVATE` creates an anonymous memory allocation for every page
written when the backing store is a file. This CoW behaviour is useful
but leads to double allocations when the backing store is an empty file
created by `memfd_create()`. When the page is written to, the CoW
semantics require a real page to be created in the memory for the memfd
(previously before the page was touched they would all point to the zero
page). This real page is filled with zeroes because in theory this page
would be accessible via read/write syscalls on the FD even though in our
implementation it is only ever `mmap()`ed.

The intention of the commit was to enable `fallocate()` to be used to
punch holes but that would only affect the inaccessible backing page and
the page in the CoW anonymous memory would be unaffected. Leading it
likely not to have the desired effect.

Fixes: #8211

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-18 07:23:36 -07:00
Chris Webb
7b29691932 vmm: return all-ones for unregistered PIO reads
When reading from an unregistered PIO address, pio_read() wasn't
initialising the buffer, so guests were reading stale bytes from the
previous PIO transaction rather than all 0xff bytes like master abort
on real hardware.

Fill data with 0xff on invalid reads.

Correct 'read to unregistered address' info message to 'read from
unregistered address' while we're touching this block.

Signed-off-by: Chris Webb <chris@arachsys.com>
2026-05-16 15:40:55 +00:00
Chris Webb
4091e965b8 vmm: return all-ones for unregistered MMIO reads
When reading from an unregistered MMIO address, mmio_read() wasn't
initialising the buffer, so guests were reading stale bytes from the
previous MMIO transaction rather than all 0xff bytes like master abort
on real hardware.

Fill data with 0xff on invalid reads.

Correct 'read to unregistered address' info message to 'read from
unregistered address' while we're touching this block.

Signed-off-by: Chris Webb <chris@arachsys.com>
2026-05-16 15:40:55 +00:00
Muminul Islam
fba55b3d9f tests: poll for source VM exit after live-migration
The post-migration check used a fixed `thread::sleep(3s)` followed by
`try_wait()` to verify the source VM had exited cleanly. That window
is too tight when the source process is the release binary used by
`test_live_upgrade_*` (i.e. `~/workloads/cloud-hypervisor-static`,
pinned to `migratable_version`).

The released binary is older than the locally-built destination and
its virtio-device teardown (resume-paused-thread -> kill -> join
across pmem, block, net, console, rng workers) regularly takes
longer than 3s on contended hosts, causing the test to report:

  thread 'common_parallel::test_live_upgrade_basic' panicked:
  Test failed: source VM was not terminated successfully.

even though the source process eventually exits with status 0.

Replace the fixed sleep with a `wait_until(Duration::from_secs(30),
...)` poll that returns as soon as `try_wait()` reports a reaped
child, then keep the existing `success()` check on the exit status.
This makes the assertion robust against the slower release-binary
shutdown path while still failing fast on a genuine error.

The same pattern was duplicated across eight migration helpers plus
the virtio-fs migration variant; convert all nine call sites for
consistency.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-16 00:19:45 +00:00
Philipp Schuster
2168ddf16b vmm: migration: better observe and log invalid states
This increases debugability.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-15 17:59:02 +00:00
Philipp Schuster
5899d062bc vm-migration: be explicit about commands
Reordering commands or adding commands in-between is breaking the
migration protocol. By using explicit numbers, we can increase the
attention required when touching this code.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-15 17:59:02 +00:00
Philipp Schuster
ba04c4f318 virtio-devices, vmm: replace Vec<T> with Box<[T]> in config structs
I started by looking at all `Option<Vec<T>>` values in config.rs and
vm_config.rs, and replaced them with `Option<Box<[T]>>`. This has the
advantage that one now can see at a glance if this field will ever
resize during operation or not, reducing cognitive load and increasing
maintainability. All fields that need the properties of a Ver or where
this change was not trivial are kept intact.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-15 16:05:01 +00:00
Philipp Schuster
29e2319247 vmm: store CPU affinity lists as slices
Each affinity host CPU list is copied from configuration.
It is only iterated afterwards, so a boxed slice is enough.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-15 16:05:01 +00:00
Philipp Schuster
74392eccae vmm: store PCI segments as a slice
The PCI segment list is created once from the configured count.
Later code mutates entries, but does not add or remove segments.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-15 16:05:01 +00:00
Philipp Schuster
c2c3178038 vmm: store PCI MMIO allocators as slices
The allocator lists are sized from the fixed PCI segment count.
They are only indexed afterwards, so boxed slices fit the use.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-15 16:05:01 +00:00
Philipp Schuster
8574bf4c13 docs: remove LTS information from releases.md
Cloud Hypervisor doesn't do LTS releases for quite some time now. This
outdated information already caused confusion [0].

[0] https://github.com/NixOS/nixpkgs/pull/520434#issuecomment-4460008719

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-15 15:40:01 +00:00
dependabot[bot]
078d57e6b9 build(deps): bump the non-rust-vmm group across 2 directories with 7 updates
Bumps the non-rust-vmm group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_with](https://github.com/jonasbb/serde_with) | `3.18.0` | `3.19.0` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.61` | `1.2.62` |
| [hybrid-array](https://github.com/RustCrypto/hybrid-array) | `0.4.11` | `0.4.12` |
| [winnow](https://github.com/winnow-rs/winnow) | `1.0.0` | `1.0.2` |
| [zvariant](https://github.com/z-galaxy/zbus) | `5.10.1` | `5.11.0` |

Bumps the non-rust-vmm group with 3 updates in the /fuzz directory: [serde_with](https://github.com/jonasbb/serde_with), [cc](https://github.com/rust-lang/cc-rs) and [hybrid-array](https://github.com/RustCrypto/hybrid-array).


Updates `serde_with` from 3.18.0 to 3.19.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.18.0...v3.19.0)

Updates `cc` from 1.2.61 to 1.2.62
- [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.61...cc-v1.2.62)

Updates `hybrid-array` from 0.4.11 to 0.4.12
- [Changelog](https://github.com/RustCrypto/hybrid-array/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/hybrid-array/compare/v0.4.11...v0.4.12)

Updates `serde_with_macros` from 3.18.0 to 3.20.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.18.0...v3.20.0)

Updates `winnow` from 1.0.0 to 1.0.2
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v1.0.0...v1.0.2)

Updates `zvariant` from 5.10.1 to 5.11.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant-5.10.1...zvariant-5.11.0)

Updates `zvariant_derive` from 5.10.1 to 5.11.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant_derive-5.10.1...zvariant_derive-5.11.0)

Updates `serde_with` from 3.18.0 to 3.19.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.18.0...v3.19.0)

Updates `cc` from 1.2.61 to 1.2.62
- [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.61...cc-v1.2.62)

Updates `hybrid-array` from 0.4.11 to 0.4.12
- [Changelog](https://github.com/RustCrypto/hybrid-array/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/hybrid-array/compare/v0.4.11...v0.4.12)

Updates `serde_with_macros` from 3.18.0 to 3.20.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.18.0...v3.20.0)

---
updated-dependencies:
- dependency-name: serde_with
  dependency-version: 3.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.62
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: hybrid-array
  dependency-version: 0.4.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with_macros
  dependency-version: 3.20.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: winnow
  dependency-version: 1.0.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zvariant
  dependency-version: 5.11.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zvariant_derive
  dependency-version: 5.11.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: serde_with
  dependency-version: 3.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.62
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: hybrid-array
  dependency-version: 0.4.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with_macros
  dependency-version: 3.20.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-15 00:59:24 +00:00
Wei Liu
7d7f24382c tests: add block device integration tests
Assisted-by: Claude:Opus-4.7
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-05-14 22:35:02 +00:00
Wei Liu
2fe775fce2 block: use BLKDISCARD/BLKZEROOUT ioctls for block devices
Some block devices (ZFS volume) may require BLKDISCARD and BLKZEROOUT
ioctls for discard and write_zeroes operations respectively.

There is no good way to probe whether fallocate is supported on a block
device. Arguably, punch_hole and write_zeroes are rare. Instead of
having a complex scheme for the IO uring backend, we force it to always
use ioctls. The code can be changed if the synchronized ioctls become a
performance issue.

Changes:
- Detect block devices at construction time
- Use BLKDISCARD ioctl for punch_hole (discard) on block devices
- Use BLKZEROOUT ioctl for write_zeroes on block devices
- Add BLKDISCARD/BLKZEROOUT to VirtioBlock seccomp whitelist
- Keep fallocate() path for regular files (no behavior change)
- Consolidate some helper functions to the new sparse module

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-05-14 22:35:02 +00:00
Wei Liu
12919dbce9 block: extract is_block_device() helper and dedupe inline fstat probes
probe_sparse_support() and DiskTopology::is_block_device() each carry
their own copy of the same fstat()+S_IFMT dance to ask "is this fd a
block device?". Hoist a single pub helper

    pub(crate) fn is_block_device(fd: RawFd) -> bool

into block::lib and route both call sites through it. Drop the
MaybeUninit gymnastics in favour of mem::zeroed() since libc::stat is
POD.

Drop DiskTopology::is_block_device since it is now just a one line
wrapper around the new helper function.

Pure refactor in preparation for the BLKDISCARD/BLKZEROOUT support,
which needs the same probe in three more backends.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-05-14 22:35:02 +00:00
Bo Chen
1314ac883c build: Release v52.0
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-05-14 19:53:22 +00:00
Dylan Reid
6b44e7b190 block: raw_async: reject batch atomically when SQ lacks capacity
submit_batch_requests pushed each BatchRequest into the io_uring SQ in
turn and used `?` to bail on the first push failure.
Leaving the initial SQEs visible to the kernel — but submitter.submit()
was never called, and every other call site in this file gates submit()
behind a preceding sq.push() that now also fails on the full ring.

This could allow a guest to DoS it's own queue or worse if the buffer is
freed early.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-14 19:53:22 +00:00
Dylan Reid
273e6d53b8 block: AlignedOperation owns its bounce buffer via Drop
The bounce buffer for an unaligned descriptor was allocated in
execute_async and leaked on error paths, even though, for the sync case
the kernel already had a pointer to the buffer.

Clean this up by moving ownership of the buffer to the AlignedOperation
type. To make it actually safe, stop stashing a guest memory pointer for
the duration of the op. Instead, save the guest address and pass guest
memory back to the complete function.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-14 19:53:22 +00:00
Dylan Reid
094f214f78 virtio-devices: block: track non-batch inflight reqs immediately
For non-batch backends execute_async submits the kernel I/O inline
before returning. An early return while processing before inserting in
inflight_requests, meant the request went untracked, the local batch
list was never appended to inflight_requests, even though the request is
pending in the kernel.

To track it, insert into self.inflight_requests as soon as execute_async
returns Ok. The completion path's find_inflight_request now matches the
orphan and the bounce buffer is freed only after the kernel signals it
is done.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-14 19:53:22 +00:00
Bo Chen
730677730e virtio-devices: block: reject duplicate in-flight head_index
A malicious or buggy guest can violate virtio by making the same
descriptor head available twice before the first chain has been placed
on the used ring. The submit path pushed both chains onto the
VecDeque-backed inflight_requests keyed by head_index, and on completion
find_inflight_request() returned the first linear match. That Request's
complete_async() freed its bounce buffer while the other chain's
io_uring op was still targeting it, producing a use-after-free the
kernel could then scribble into.

Signed-off-by: Dylan Reid <dgreid@fb.com>
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-05-14 19:53:22 +00:00
Bo Chen
b4b99824d6 vmm: Deprecate image type auto detection
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-05-14 18:18:14 +00:00
Anatol Belski
9d487a8abc vmm: Disable sector 0 writes for autodetected VHD images
When no image_type is specified, sector 0 writes are disabled as a
safety measure for autodetected raw images. Extend this protection
to autodetected fixed VHD images, which carry metadata in the last
sector and are equally susceptible to accidental overwrites of the
first sector when the format is not explicitly acknowledged.

Update the corresponding warning in the virtio block worker to be
format agnostic.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-14 17:27:04 +00:00
CMGS
1ad9920713 vmm: document move_bar allocator rollback paths
Explanatory comments for the rollback paths in both PCI BAR
relocation branches.

Assisted-by: Claude (Anthropic)
Signed-off-by: CMGS <ilskdw@gmail.com>
2026-05-14 15:54:12 +00:00
CMGS
e65cca3bf5 vmm: roll back mem64/IO allocator on failed move_bar
After free(old_base), if allocate(new_base) fails the allocator
treats old_base as free even though the MMIO/PIO bus still maps the
device there. Subsequent allocations pick old_base, mmio_bus.insert
hits the live mapping and returns Overlap.

Restore old_base on the failure path in both the Memory*BitRegion
and IoRegion branches before bubbling the error up.

PR #7950 added restore_bar_addr() so the BAR config register stays
consistent on failed move_bar(); this completes the same picture
for the allocator side.

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-05-14 15:54:12 +00:00
Alyssa Ross
4efb8b7951 virtio-devices: ack requests from backends
Quoting the spec:

> If VHOST_USER_PROTOCOL_F_REPLY_ACK is negotiated, and the back-end
> sets the VHOST_USER_NEED_REPLY flag, the front-end must respond with
> zero when operation is successfully completed, or non-zero
> otherwise.

cloud-hypervisor would previously not send a response to a
VHOST_USER_BACKEND_CONFIG_CHANGE_MSG message, even if
VHOST_USER_PROTOCOL_F_REPLY_ACK had been negotiated and
VHOST_USER_NEED_REPLY was set, in violation of the spec.

Link: https://qemu-project.gitlab.io/qemu/interop/vhost-user.html#back-end-message-types
Fixes: 8d6213338 ("virtio-devices: generic-vhost-user: Config change notification")
Signed-off-by: Alyssa Ross <hi@alyssa.is>
2026-05-14 15:53:24 +00:00
Alyssa Ross
1ad68e8df1 misc: gitlint: allow Closes and Link trailers
Both of these might be used with long URLs.

Signed-off-by: Alyssa Ross <hi@alyssa.is>
2026-05-14 15:56:04 +01:00
Philipp Schuster
b0f92d01fc tests: avoid event wait log spam
Event expectation helpers print detailed diagnostics when the observed
event stream does not match the expected one. That is useful for direct
assertions, but it becomes extremely noisy [0] when the helper is used
as the predicate for wait_until(), because every polling attempt emits
the full mismatch dump.

Add quiet wait wrappers for event polling and emit the existing detailed
diagnostics only once after the timeout expires.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/actions/runs/25745401604/job/75619840718?pr=8021

On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-13 22:16:29 +00:00
Muminul Islam
96c83168a1 tests: Avoid IPv6 autoconf races in test_vdpa_net
The test_vdpa_net integration test brings the vDPA-backed interface
(ens6) up and then asserts that both TX and RX packet counters are
exactly zero before sending an explicit ping. On guest kernels that
perform IPv6 link-local autoconfiguration quickly enough, however,
Router Solicitation / Neighbor Discovery frames are emitted as soon
as the link comes up. The vdpa_sim_net device loops those frames back
to the interface, so by the time the test queries

    ip -j -p -s link show ens6 | grep -c '"packets": 0'

the TX and RX counters are already non-zero and the precondition
assertion fails (observed reliably with the Microsoft internal guest
kernel running on MSHV).

Disable IPv6 / accept_ra / autoconf on ens6 before bringing the link
up. With IPv6 disabled no autoconf traffic is generated, the counters
remain at zero until the explicit 'ping 172.16.1.10 -c 6' generates
exactly the 6 packets the rest of the test expects on each direction,
and the vDPA-specific portion of the test is unchanged.

Verified on an MSHV Azure VM (Linux 6.6.121.mshv2):

    test common_parallel::test_vdpa_net ... ok
    test result: ok. 1 passed; 0 failed; ...; finished in 26.13s

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-13 22:15:19 +00:00
Julian Schindel
466d9491c3 main: Use UTC in wallclock time log
Using `jiff::Timestamp::now()` instead of `jiff::Zoned::now()` skips the
timezone logic required for `Zoned`. This makes the timestamp UTC, with
the appropriate `Z` suffix.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
2026-05-13 22:14:57 +00:00
Dylan Reid
1176552b6e virtio-devices: vhost_user: refuse activate when disconnected
If a guest observes DEVICE_NEEDS_RESET, resets the device, and tries to
re-initialize it, but the VMM knows the backend is disconnected, we can
short-circuit the doomed activation.

This is not incorrect, but saves the VMM from making several round-trip
calls to a peer process that doesn't exist. It'll also make the logs
cleaner.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-13 22:14:52 +00:00
Dylan Reid
99ec20ff97 virtio-devices: vhost_user: skip resume for disconnected backends
resume() mirrors pause() for backend communication: it skips the
vhost-user backend call when the device is already disconnected, and it
marks newly failed resume_vhost_user() calls disconnected only when the
classifier identifies transport loss.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-13 22:14:52 +00:00
Dylan Reid
f56bfdaeb7 virtio-devices: vhost_user: skip pause for disconnected backends
pause() returns DeviceDisconnected without calling into the backend when
VhostUserCommon already knows the socket is gone. DeviceManager treats
only that sentinel as log-and-continue, so one dead vhost-user device
does not abort the whole pause iteration.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-13 22:14:52 +00:00
Dylan Reid
db79150303 virtio-devices: vhost_user: memory update error handling
For add memory region, if the backend is disconnected or returns an
error, forward the appropriate error type to the caller. If the error
indicates that the vhost user backend has disconnected, mark it as such.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-13 22:14:52 +00:00
Dylan Reid
c03e7055cd virtio-devices: vhost_user: skip backend reset when disconnected
reset() is teardown and must still clean up local state even if the
vhost-user backend has already gone away. When the disconnected flag is
already set, it skips reset_vhost_user() and proceeds with kill-event,
worker-unblock, event logging, and interrupt callback cleanup.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-13 22:14:52 +00:00
Dylan Reid
198ab1447d virtio-devices: vhost_user: remove unused restore_backend_connection
This function hasn't been used since '22.
All callers removed with:
1f0e5eb66 vmm: virtio-devices: Restore every VirtioDevice upon creation

TEST: build and cargo test all still pass, grep returns no results.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-13 22:14:52 +00:00
Dylan Reid
4f60379142 virtio-devices: vhost_user: classify disconnected backend errors
Add two explicit disconnected-backend error paths before wiring them
into the call sites.

MigratableError::DeviceDisconnected is the lifecycle sentinel for
operations that were skipped because a component is already known to be
disconnected. It lets the caller log and continue without treating it as
a VMM-fatal condition.

Error::BackendDisconnected is the vhost-user-local error used when
VhostUserCommon refuses to call a backend after its disconnected flag is
set. The transport classifier treats socket close/reset/EOF and
vhost-user partial-message/disconnected cases as transport loss, while
backend NACKs, invalid protocol state, and retry-able socket errors
remain ordinary operation failures.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-13 22:14:52 +00:00
Dylan Reid
45fdc276fe virtio-devices: vhost_user: track backend disconnected state
Add a 'disconnected' flag shared between VhostUserCommon and
VhostUserEpollHandler. This flag is set whenever the run loop hits an
error that would cause an exit (failed reconnect, broken backend req
handler, unknown event).

Following commits will use this to gate backend calls in order to avoid
repeated timeouts and errors when a backend disappears. This will
simplify shutdown sequencing for orchestrators using vhost-user devices.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-13 22:14:52 +00:00
Saravanan D
aca58641ee ci: Exclude Slack invite URLs from link checker
Slack's join.* endpoints reject automated GETs
and return 403, so lychee was failing the link availability
check on any PR that touched README.md

Add the join.slack.com/t/ prefix to the .lychee.toml
exclude list

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-05-13 22:14:22 +00:00
Saravanan D
2f4b07b8f3 docs: Add prebuilt firmware and AArch64 notes
cloud-hypervisor/edk2 publishes prebuilt CLOUDHV.fd (x86-64) and
CLOUDHV_EFI.fd (AArch64) as release assets. docs/uefi.md only
described the build from source, and the AArch64 firmware
customizations required for cloud-hypervisor were left undocumented.

Add a "Using Prebuilt UEFI Firmware" section to docs/uefi.md and
an "AArch64 Firmware Notes" section covering both customizations.

Updates to "Building UEFI Firmware for AArch64" section.

Switch the boot examples from --kernel to --firmware, which is the
direct UEFI load path on AArch64.

Minor README.md updates.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-05-13 22:14:22 +00:00
Bo Chen
05b8464295 build: Add .editorconfig to REUSE dep5
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-05-13 21:14:47 +00:00
Bo Chen
88d3eaa509 build: Temporarily make windows integration job non-blocking
See: #8211

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-05-13 21:14:47 +00:00
Bo Chen
3645d916f3 tests: Stabilize VFIO NVIDIA memory hotplug check
Bump the wait_until timeout for the guest-visible memory growth in
test_nvidia_card_memory_hotplug from 5s to 15s. The hot-add path inside
the guest kernel can take longer than 5s particularly when using
virtio-mem, which has been a source of flakes for this test.

Drop the trailing assert!(guest.get_total_memory() > 5_760_000), as it
is redundant.

See: #8160

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-05-13 21:14:47 +00:00
Bo Chen
9ca5f63fe0 scripts: namespace dev_cli tmp dir per user and drop dead BUILD_DIR
The shared /tmp/cloud-hypervisor/ path is created by the first user to
run dev_cli.sh and owned by them, so other users on the same host fail
the +x+w check and cannot chmod it back. Move both tmp paths under
/tmp/cloud-hypervisor-${USER}/ so each user gets their own tree.

While here, fold the local BUILD_DIR in build_container() into the
existing (previously unused) CLH_CTR_BUILD_DIR, which ensure_build_dir()
already creates.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-05-13 21:14:47 +00:00
Philipp Schuster
0638b38d60 misc: sort .gitignore alphabetically
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-13 16:58:47 +00:00
Philipp Schuster
9ad057bd14 misc: add LLM agent artifacts to .gitignore
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-13 16:58:47 +00:00
Philipp Schuster
117ed4e9bc misc: fix formatting in a couple of *.toml files
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-13 16:58:47 +00:00
Philipp Schuster
fc8fd3682a misc: sync .taplo with .editorconfig
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-13 16:58:47 +00:00
Philipp Schuster
96700bbfc7 misc: init .editorconfig
TL;DR: Improved developer productivity for many contributors

Add a shared .editorconfig so contributors get lightweight,
editor-native hints while writing code instead of only discovering
formatting issues later in style checks.

This is advisory, but useful: editors can already guide indentation,
whitespace, line endings, and final newlines as you type. They can also
show a visual guide at the 80-character line width, which is one of the
main motivations here. Most LLM-generated code and contributions over
the past year have already conventionally followed the 80-character
limit, so this makes the expected style visible and consistent for
everyone.

.editorconfig is a decade-old standard, and virtually every editor or
IDE supports it.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-13 16:58:47 +00:00
Philipp Schuster
cde761a90d misc: add AGENTS.md for easier LLM use
Initial attempt for a lightweight AGENTS.md to improve working with
LLMs on this repository and avoid common pitfalls.

Assisted-by: Codex:GPT-5.5
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-13 16:49:47 +00:00
JP Kobryn
ced3762a67 vmm: create memfd for private mappings
User-defined zones may be mapped private. Create a memfd for private
zones so that fallocate operations are available on all regions, not
just shared ones. This prepares for zone management via hole punching.

The MAP_ANONYMOUS flag is now omitted since the memory becomes
tmpfs-backed via memfd.

Signed-off-by: JP Kobryn <inwardvessel@gmail.com>
2026-05-12 11:47:18 +00:00
Anatol Belski
534aaaceb2 virtio-devices: Require at least one ready queue for activation
When a guest resets a device by writing status=0 and reinitializes
without enabling queues before writing DRIVER_OK, the activation
path would collect zero ready queues and treat that as a fatal
error, killing the entire VMM process.

The PCI transport now checks that at least one queue is ready
before reporting that the device needs activation. This prevents
a spurious activation attempt that would fatally fail when no
queues are enabled.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-12 13:53:33 +01:00
Philipp Schuster
9a46affc33 vmm: add missing ID validation for virtio-rng device
On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-11 20:18:03 +00:00
Philipp Schuster
cecd16a84f openapi: update schemas for ConsoleConfig and SerialConfig
On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-11 20:18:03 +00:00
Philipp Schuster
0e67f27546 tests: extend test_pci_device_id() to also test virtio-console
On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-11 20:18:03 +00:00
Philipp Schuster
e793353bfb vmm, main: make PCI BDF configurable for ConsoleConfig
Add common PCI device configuration to the virtio-console device
configuration. This allows setting the device ID (the name), ID, the
PCI segment, and the PCI device ID (BDF), which were previously not
configurable for the virtio-console device.

This gives management software, such as libvirt, more control over PCI
resource assignment and aligns virtio-console with other devices that
already support this functionality [0].

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/issues/8175

On-behalf-of: Philipp Schuster <philipp.schuster@sap.com>
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-11 20:18:03 +00:00
Philipp Schuster
36a3369802 vmm: drop misleading iommu option from serial config
`--serial` still accepted `iommu=on|off` from the shared serial/console
configuration shape, even though only virtio-console has a meaningful
IOMMU connection.

Cloud Hypervisor wires virtio-iommu support through DMA-capable
virtio/VFIO PCI endpoints. The serial devices are legacy UARTs accessed
through PIO or MMIO registers, so exposing an IOMMU option there is
misleading.

On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-11 20:18:03 +00:00
Philipp Schuster
04bc6b3ccc vmm, main: split ConsoleConfig and SerialConfig into separate structs
This commit introduces a new struct `CommonConsoleConfig` which is the
base for the split into `ConsoleConfig` and `SerialConfig`. This is a
pre-requisite for allowing more configurable PCI options for the
virtio-console device.

The commit doesn't change or add any functionality.

On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-11 20:18:03 +00:00
Philipp Schuster
a56f49787c option_parser: add add_all_valueless() helper
Helpful to prevent repetitions.

On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-11 20:18:03 +00:00
Anatol Belski
fcfae4cc4b virtio-devices: Test config_generation wraps at u8 max
config_generation is a u8 and the spec mandates wrap around.
Verify the increment past 0xff lands on 0x00 without panicking
under overflow checks.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-11 20:17:40 +00:00
Anatol Belski
ecffd9494c virtio-devices: Test consume_config_change semantics
Cover the three observable cases of consume_config_change. With
the flag set the counter advances by one and the flag is cleared.
With the flag clear the call is a no-op. A burst of flag sets
between two reads results in only one bump, which is the wrap
hazard mitigation the spec asks for.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-11 20:17:40 +00:00
Anatol Belski
1ba4298551 virtio-devices: Test trigger sets config_changed flag
A Config trigger must set the config_changed flag so the next
device specific configuration read can bump config_generation. A
Queue trigger must leave the flag alone.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-11 20:17:40 +00:00
Anatol Belski
90b9481745 virtio-devices: Bump config_generation on device config read
Set a config_changed flag in VirtioInterruptMsix when a Config
interrupt fires, and increment config_generation only when the
driver next reads the device specific configuration region. The
flag is cleared by that read so the driver observes a stable
value across the read and a fresh value on any later read.

This avoids the wrap hazard of incrementing on every Config
event, where a burst of interrupts could roll the 8 bit counter
back to its previous value between two driver reads.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-11 20:17:40 +00:00
Anatol Belski
e923f9a678 virtio-devices: Make config_generation an Arc<AtomicU8>
Convert the u8 field to Arc<AtomicU8> so the interrupt path can
mutate it without holding the common config mutex. Reads at
offset 0x15 use Acquire ordering. State serialization preserves
the value.

Update the three unit tests that construct
VirtioPciCommonConfig directly.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-11 20:17:40 +00:00
Rob Bradford
03025dee58 main: Drop the "Z" suffix from wallclock time in the log
This prints the time in the local time. The "Z" suffix usually indicates
UTC time. This is left over from when there was manual implementation
using libc::gmtime()

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 18:49:17 +00:00
Rob Bradford
e0e10b5971 tests: Disable tests on ARM64 that don't work with stock kernel
An edk2 boot is needed for ACPI support but the stock ARM64 kernel does
not support ACPI memory hotplug or virtio-pmem. For now disable those
tests but track them in #8187.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 17:52:00 +00:00
Rob Bradford
9b7260e630 tests: Adapt topology tests on ARM64 for new lscpu output
The lscpu output is now different on ARM64 (referencing clusters vs
sockets).

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 17:52:00 +00:00
Rob Bradford
c392274f09 tests: Streamline watchdog tests
This is a niche feature and we were overly testing it. Let's just switch
to two tests. One for live migration and one for plain watchdog. This
will reduce the CI time. As we are now running these tests sequentially
we can also reduce some the delays in the tests.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 17:52:00 +00:00
Rob Bradford
0f7e6a0d3a tests: Move watchdog tests to sequential
These tests are very timing dependent and so need to be run without high
levels of load.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 17:52:00 +00:00
Rob Bradford
55adcc27da tests: Replace last vestiges of focal use with jammy
Focal has served us well for many years but is now beyond EOL. Remove
all remaining use of focal images from the CI.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 17:52:00 +00:00
Dylan Reid
1b5c6d9977 vmm: device_manager: always tear down vfio_user DMA handlers on eject
The PciDeviceHandle::VfioUser arm in eject_device propagated dma_unmap
failures with ?, which short-circuited the subsequent
remove_dma_mapping_handler loop and left stale Arc<VfioUserDmaMapping>
entries in every virtio-mem device's handler map.  Log unmap errors
with warn! and continue so the handler map cleanup always runs.

This only happens if a vfio-user process crashes and the same device is
later removed. However, given we've seen similar issues on vhost-user
this is probably worth cleaning up.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-11 17:40:28 +00:00
Dylan Reid
6111d549b7 vmm: device_manager: handle short ACPI hotplug MMIO width gracefully
The BusDevice read and write arms for B0EJ_FIELD_OFFSET and
PSEG_FIELD_OFFSET opened with assert!/assert_eq! on data.len(), so a
guest 1/2/8-byte MMIO access to either register panicked the vCPU
thread.  Replace each assert with a warn! and early return so unusual
access widths are logged and ignored instead of crashing the VMM.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-11 17:40:28 +00:00
Dylan Reid
e1c2b179e7 vmm: device_manager: prune balloon and virtio_mem refs on eject
self.balloon and self.virtio_mem_devices are not updated when
eject_device removes the underlying device, leaving stale
Arc<Mutex<...>> entries that resize_balloon / balloon_size and the
virtio-mem DMA-handler iteration would dereference if reached after
eject. Clear self.balloon and retain-out the matching virtio-mem entry
in the PciDeviceHandle::Virtio eject arm, identifying the ejected
device by Arc pointer-equality against the already-resolved
Arc<Mutex<dyn VirtioDevice>>.

This change is defensive: DeviceManager::remove_device currently
rejects VirtioDeviceType::Balloon and VirtioDeviceType::Mem with
RemovalNotAllowed before pci_devices_down is set, so the guest never
sees an eject notification and eject_device is never reached for
either type today. If the allowlist is extended later, this cleanup
keeps the post-eject state consistent.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-11 17:40:28 +00:00
Rob Bradford
87c546ea6a main: Add more log format options
Add {wallclock}, {pid}, and {tid} tokens to the log format system.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 12:47:22 +00:00
Rob Bradford
d9b8f28f21 main: Add --log-format
Currently only the existing format options are supported and the default
format string is unchanged. This allows more fine grained logging
control.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 12:47:22 +00:00
Rob Bradford
3bbf97c5e5 main: Add unit testing for logger infrastructure
Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 12:47:22 +00:00
Rob Bradford
a440ea0b44 main: Create a format string parser and printer for log output
Introduces a custom format string parser for use for log entries. For
now only the existing format string entries are covered and the default
format string matches the existing behaviour.

The format string is tokenized once and then that token stream is used
for each log entry.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 12:47:22 +00:00
Rob Bradford
661faf51fe main: Move logger into its own mod
In preparation for extending its functionality, refactor the Logger
struct and its implementation to a new file / module.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 12:47:22 +00:00
Dylan Reid
3fc0ff00d5 vhost_user_block: fix process_queue unwraps
process_queue() unwrapped four guest-reachable Results.

Convert each unwrap to a logged error path. A failed status write is
demoted to len = 0 so the head is still retired via add_used; an
add_used failure breaks the batch (queue is in a bad state); a
needs_notification failure  signals; a signal failure is logged. The
daemon stays up.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-11 11:48:55 +00:00
Dylan Reid
1f0a78b766 vhost_user_block: set_config tolerates partial sub-range writes
set_config() split the config slice at `offset` and copy_from_slice'd
the entire suffix, which asserts src.len() == self.len(). This panics if
the guest issues a write shorter than `config_len - offset`.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-11 11:48:55 +00:00
Rob Bradford
5ca693495a vmm: api: Log HTTP API error responses
Although a response is sent to the client these errors are not logged in
the log file making it hard to cross reference these with other log
entries.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 11:22:32 +00:00
Rob Bradford
bca9b97919 virtio-devices: vhost-user: Log when attempting reconnection
Currently when the socket is disconnected by the other end there is no
logging of such an event. Add a log to aid identifying when this has
happened.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 11:22:10 +00:00
Rob Bradford
74bf0b4a55 virtio-devices: vhost-user: Abandon reconnection if kill event sent
Abandon the reconnection to the vhost-user socket if the kill_evt is
fired because e.g. a device removal request has come in during the
reconnection.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 11:22:10 +00:00
Rob Bradford
0f61655743 virtio-devices: vhost-user: Use TimerFd connect_vhost_user
Replace the use of sleeps with a TimerFd. Initially this is functionally
equivalent but it can be extended to also handle other events.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-11 11:22:10 +00:00
Wei Liu
f67d0569b4 block: fix cargo test -p block
`RawBackend::IoUring` is only available when `io_uring` feature is
defined.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-05-09 14:10:25 +00:00
dependabot[bot]
f7ebb0bedd build(deps): bump crate-ci/typos from 1.46.0 to 1.46.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.46.0 to 1.46.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](bbaefadf97...5374cbf686)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.46.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-09 00:36:06 +00:00
Anatol Belski
cfec130772 virtio-devices: balloon: Cap inflate and deflate descriptor length
Drop inflate or deflate descriptors whose len exceeds the Linux
driver maximum of VIRTIO_BALLOON_ARRAY_PFNS_MAX PFN entries of 4
bytes each. Without the cap, a guest can submit a descriptor with
a huge len over a small backing and drive an unbounded warn loop
in the device thread.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-08 22:11:54 +00:00
Philipp Schuster
bad10f3026 main, vmm: add explicit PCI BDF support for Rng device
This was missing in [0] but is required for proper explicit PCI BDF
management, e.g., when a VM is created via libvirt and each device has
an explicit BDF.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7965

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-08 19:39:18 +00:00
Philipp Schuster
18bbc71b59 hypervisor: fix typo
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>

xxx ci
2026-05-08 16:29:18 +00:00
Philipp Schuster
3ebd32844e vm-allocator, vmm: InterruptRoute: free GSIs on drop
These changes free GSIs when they are no longer used. That way we
won't exhaust the available GSIs anymore when attaching and
detaching devices.

Now, once can add devices and remove them hundreds of times, without
running out of GSIs.

Co-authored-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>

On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-08 16:29:18 +00:00
Philipp Schuster
1ba2b34019 vm-allocator: free GSIs in GsiAllocator
The old implementation used an ever monotonically increasing u32 counter
to allocate new GSIs. The counter increased every time a new GSI was
allocated, and freeing GSIs was not possible. Thus, Cloud Hypervisor
can run out of GSIs and panics. This currently happened at the 1024th
GSI [0]. Further, this caused the `KVM_SET_GSI_ROUTING` ioctl to carry
much more payload than needed.

This new implementation uses a bitmap for proper tracking of resources
and can gracefully free GSIs - this is abstracted in type
InterruptAllocator.

Please note that this commit only replaces the old mechanism. The next
commit will introduce freeing used GSIs automatically when an
InterruptRoute is dropped.

While being on this, we also propagate the errors that the allocator may
throw where necessary.

Co-authored-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>

On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-08 16:29:18 +00:00
Philipp Schuster
7c4fa04ea3 vm-allocator: introduce bitmap-backed InterruptAllocator
This introduces a bitmap-backed interrupt number allocator. The type is
not yet used but will be in the next commit and enable graceful
releasing of allocated interrupt numbers (GSIs).

Co-authored-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>

On-behalf-of: Philipp Schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-08 16:29:18 +00:00
dependabot[bot]
90859452ab build(deps): bump the non-rust-vmm group across 2 directories with 11 updates
Bumps the non-rust-vmm group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [zbus](https://github.com/z-galaxy/zbus) | `5.14.0` | `5.15.0` |
| [digest](https://github.com/RustCrypto/traits) | `0.11.2` | `0.11.3` |
| [hybrid-array](https://github.com/RustCrypto/hybrid-array) | `0.4.10` | `0.4.11` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.95` | `0.3.97` |
| [openssl-sys](https://github.com/rust-openssl/rust-openssl) | `0.9.114` | `0.9.115` |

Bumps the non-rust-vmm group with 1 update in the /fuzz directory: [js-sys](https://github.com/wasm-bindgen/wasm-bindgen).


Updates `zbus` from 5.14.0 to 5.15.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus-5.14.0...zbus-5.15.0)

Updates `digest` from 0.11.2 to 0.11.3
- [Commits](https://github.com/RustCrypto/traits/compare/digest-v0.11.2...digest-v0.11.3)

Updates `hybrid-array` from 0.4.10 to 0.4.11
- [Changelog](https://github.com/RustCrypto/hybrid-array/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustCrypto/hybrid-array/compare/v0.4.10...v0.4.11)

Updates `js-sys` from 0.3.95 to 0.3.97
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `openssl-sys` from 0.9.114 to 0.9.115
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.114...openssl-sys-v0.9.115)

Updates `wasm-bindgen` from 0.2.118 to 0.2.121
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.118...0.2.121)

Updates `wasm-bindgen-macro` from 0.2.118 to 0.2.121
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.118...0.2.121)

Updates `wasm-bindgen-macro-support` from 0.2.118 to 0.2.121
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.118...0.2.121)

Updates `wasm-bindgen-shared` from 0.2.118 to 0.2.121
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.118...0.2.121)

Updates `winnow` from 0.7.15 to 1.0.0
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.7.15...v1.0.0)

Updates `zbus_macros` from 5.14.0 to 5.15.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus_macros-5.14.0...zbus_macros-5.15.0)

Updates `js-sys` from 0.3.95 to 0.3.97
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `wasm-bindgen` from 0.2.118 to 0.2.121
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.118...0.2.121)

Updates `wasm-bindgen-macro` from 0.2.118 to 0.2.121
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.118...0.2.121)

Updates `wasm-bindgen-macro-support` from 0.2.118 to 0.2.121
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.118...0.2.121)

Updates `wasm-bindgen-shared` from 0.2.118 to 0.2.121
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.118...0.2.121)

---
updated-dependencies:
- dependency-name: zbus
  dependency-version: 5.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: digest
  dependency-version: 0.11.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: hybrid-array
  dependency-version: 0.4.11
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.97
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: openssl-sys
  dependency-version: 0.9.115
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.121
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.121
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.121
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.121
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: winnow
  dependency-version: 1.0.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: zbus_macros
  dependency-version: 5.15.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.97
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.121
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.121
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.121
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.121
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-08 00:48:40 +00:00
Rob Bradford
49259bb84f hypervisor: kvm: Punch holes in guest_memfd on shared transition
When an SEV-SNP guest transitions pages from private to shared via
KVM_HC_MAP_GPA_RANGE, punch holes in the corresponding guest_memfd
backing it. Without this the balloon driver's `set_memory_decrypted()`
path transitions the page attributes but the physical memory stays
pinned in guest_memfd, making virtio-balloon ineffective for memory
overcommit with confidential VMs. Even without ballooning these pages
are unused by the guest so consume resources. This mirrors the hole
punching that the balloon device does for releasing pages.

The memory_slots Arc is cloned into each KvmVcpu at creation so the
punch can happen in the vcpu thread.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-07 22:48:06 +00:00
Rob Bradford
ebeef03192 hypervisor: kvm: Introduce KvmMemorySlot to track guest_memfd per slot
Replace the bare OwnedFd map (guest_memfds) with a KvmMemorySlot struct
wrapped in an Arc so it can later be shared with KvmVcpu. This is a
pure refactor with no functional change; KvmMemorySlot currently holds
only the guest_memfd OwnedFd.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-07 22:48:06 +00:00
Anatol Belski
26ed2a98bc virtio-devices: block: Use Display when logging Error
block::Error implements Display via thiserror, so the user facing
log lines do not need the Debug formatter.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-07 22:00:32 +00:00
Anatol Belski
14dfd78f04 virtio-devices: Drop unused device_needs_reset helper
With the per handler needs_reset() gates removed from net and
block, nothing reads DEVICE_NEEDS_RESET anymore. Drop the
device_needs_reset helper and refresh the doc comment on
mark_device_needs_reset to reflect the central call site in
spawn_virtio_thread, where it runs after the worker has already
exited.

No functional change.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-07 22:00:32 +00:00
Anatol Belski
293672ea76 virtio-devices: block: Drop per handler NEEDS_RESET bookkeeping
With virtqueue iterator errors now killing the worker and
spawn_virtio_thread marking NEEDS_RESET centrally, the per
handler needs_reset() gate on process_queue_submit and
process_queue_complete is unreachable.

Drop needs_reset(), the two early returns, the unused
device_status field on BlockEpollHandler and its initializer,
and the device_needs_reset import.

No functional change.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-07 22:00:32 +00:00
Anatol Belski
0597e72974 virtio-devices: block: Surface virtqueue iterator errors to the worker
queue.iter() errors used to be swallowed by
handle_queue_iterator_error(), which marked the device as
NEEDS_RESET and returned Ok so the worker kept running while
disabled. spawn_virtio_thread now does the NEEDS_RESET marking
when the worker exits with an error.

Propagate the iterator error as Error::QueueIterator and escalate
it to EpollHelperError::HandleEvent in
process_queue_submit_and_signal so the worker exits. Per request
errors stay logged. Drop the now unused
handle_queue_iterator_error helper.

No functional change for the guest. NEEDS_RESET is still set and
the config interrupt is still raised on virtqueue corruption.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-07 22:00:32 +00:00
Anatol Belski
6669ec1b86 virtio-devices: net: Drop per handler NEEDS_RESET bookkeeping
spawn_virtio_thread now marks the device as NEEDS_RESET and notifies
the guest whenever the worker thread exits with an error, so the
per handler needs_reset() gate and the handle_queue_iterator_error()
helper in net are redundant.

Let virtqueue iterator errors propagate out of the worker thread
through DeviceError::NetQueuePair. Drop the unused device_status
field and the device_needs_reset and mark_device_needs_reset
imports.

No functional change for the guest. NEEDS_RESET is still set and
the config interrupt is still raised on virtqueue corruption.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-07 22:00:32 +00:00
Roberto Campesato
fff674a27d vmm: prefer /dev/userfaultfd over the syscall
Prefer /dev/userfaultfd (Linux 6.1+) over the userfaultfd(2) syscall
for obtaining userfaultfd file descriptors.  The device path bypasses
the capable(CAP_SYS_PTRACE) and vm.unprivileged_userfaultfd sysctl
checks that block the syscall in user-namespaced containers, using
file permissions instead.

Falls back to the syscall on older kernels or when the device node
does not exist.

Signed-off-by: Roberto Campesato <render@metalabs.org>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 21:37:46 +00:00
Muminul Islam
df1809c1fa scripts: limit integration tests to cloud-hypervisor package
Pass `-p cloud-hypervisor` to all `cargo nextest run` invocations in
the integration test scripts so test discovery and execution are
scoped to the cloud-hypervisor package only, avoiding running tests
from other workspace crates.

This avoids flooding the output messages like below
`test result: ok. 0 passed; 0 failed; 0 ignored;
0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs`

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-07 17:44:38 +00:00
Rob Bradford
0ca06517f8 tests: Cleanup macvtap interfaces
Clean up the macvtap interfaces that may have been left from a previous
failed run. Failure to clean those up guarantees that the subsequent
test runs will fail.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-07 17:30:53 +00:00
Keith Adler
26c9a54833 main: use path placeholders in help text
Use concise <path> placeholders in socket and console help strings
instead of wording every path as a file. This keeps the established
file= config key intact while making the user-facing help wording
match the path terminology requested in the issue.

Signed-off-by: Keith Adler <kadler@cloudflare.com>
2026-05-07 16:46:41 +00:00
Philipp Schuster
a67277b9cd hypervisor: kvm: improve logging for triple fault
When a triple fault happens [0], we now get at least a log message. This
helps to better understand the root cause of sudden reboots.

Broader context: We experience reboots caused by triple faults in
edk2 (6 months old as well as recent). They happen so early in the boot
that one doesn't really see them without looking at the VMM log. An
automatic system reset plus reboot often hides these situations - now
they are at least more visible in the log.

PS: Printing the registers to get more debugging help doesn't help, as
the guest already triple-faulted - the CPU state of the root cause
doesn't exist anymore.

[0] https://elixir.bootlin.com/linux/v6.18.6/source/arch/x86/kvm/x86.c#L11123

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-05-07 16:43:04 +00:00
Keith Adler
f3263d0988 block: preserve async queue push errors
Keep the underlying io_uring submission queue push error in raw async
I/O paths instead of replacing it with a generic full-queue message.

Signed-off-by: Keith Adler <kadler@cloudflare.com>
2026-05-07 16:37:57 +00:00
Rob Bradford
cd3aca91d3 virtio-devices: pmem: Respond to unknown request types
Request::parse rejected unknown request types with an error, causing
process_queue to report the chain as used with no response written.
The device should write an error response so the driver knows the
request was handled. Move type validation out of parse into
process_queue where a proper response can be constructed.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
56c18b51cc virtio-devices: console: Handle output queue errors gracefully
Address translation, guest memory read, write, and flush failures on
the transmitq propagated errors that killed the console device thread.
A host-side I/O error such as a PTY disconnect would permanently
disable the console. Log warnings and continue processing instead.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
4725a997cc virtio-devices: console: Handle input queue errors gracefully
Address translation or guest memory write failures on the receiveq
propagated errors that killed the console device thread. Log a
warning and break out of the descriptor loop instead.

Also fix a data-loss bug: bytes were drained from the input buffer
before the write to guest memory, so a failed write would silently
discard the data. Copy first, write, then drain only on success.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
48123669b5 virtio-devices: console: Skip device-writable output descriptors
The output queue handler read data from every descriptor without
checking the write-only flag. The driver must not put device-writable
buffers in the transmitq. Skip them with a warning.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
521b15bda6 virtio-devices: console: Skip device-readable descriptors in input queue
The input queue handler wrote data to every descriptor without
checking the write-only flag. The device must not write to
device-readable buffers. Skip them with a warning.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
fe7745b472 virtio-devices: mem: Remove dead error variants and unused import
Remove UnknownRequestType (no longer returned after responding with
ERROR), EventFdWriteFail, EventFdTryCloneFail, MpscRecvFail, and
NotActivatedByGuest which have no call sites. Drop the now-unused
mpsc import.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
6a1fe123c2 virtio-devices: mem: Handle descriptor parse errors gracefully
A malformed descriptor chain — wrong read/write flags, missing
descriptors, or undersized buffers — caused Request::parse to return
an error that killed the device thread. Log a warning, report the
chain as used with zero length, and continue processing.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
dfee31a84c virtio-devices: mem: Use usize for block counts in internal bitmap APIs
is_range_state() and set_range() took nb_blocks as u16 to match the
wire format, but unplug_all() computed the total block count from
region_size / block_size and cast to u16, silently truncating for
regions larger than 128 GiB. Widen the internal parameter to usize
so unplug_all() resets the full bitmap.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
1dd588fa6d virtio-devices: mem: Return early in state_request for invalid ranges
When is_valid_range() returns false, the handler still computed
offset = addr - config.addr which can underflow if addr is below the
region base, then queried the bitmap at a meaningless index. Return
(ERROR, 0) immediately so no arithmetic runs on invalid input.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
97e16f249f virtio-devices: mem: Respond to unknown request types
An unknown request type caused process_queue to return an error that
killed the device thread. The request/response descriptors are already
parsed at this point, so respond with VIRTIO_MEM_RESP_ERROR and
continue processing.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
ce74caa6c2 virtio-devices: balloon: Handle reporting queue errors gracefully
Address translation or memory release errors on the free page
reporting queue propagated up and killed the balloon device thread.
Log a warning and skip the offending descriptor so the device
continues operating.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
c1b5b18d9c virtio-devices: balloon: Handle invalid inflate/deflate input
A single malformed descriptor — device-writable where device-readable
is expected, non-aligned length, overflowed address, unmapped PFN, or
a failed fallocate/madvise — propagated an error that killed the
balloon device thread. Skip bad descriptors and PFNs with a warning
so the device keeps operating.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
52db3f8655 virtio-devices: balloon: Process all inflate/deflate descriptors
Only the first descriptor in each chain was processed. If the PFN
array spanned multiple chained descriptors the remaining PFNs were
silently dropped. Iterate the full chain so every descriptor is
validated and its PFNs are acted on.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
ff78eb8fb4 virtio-devices: balloon: Report used length of 0 for inflate/deflate
The inflate and deflate queues carry device-readable PFN arrays: the
driver writes them and the device only reads. The device never writes
to the descriptor buffers, so the used ring entry should report 0
bytes written rather than the descriptor length.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
7c8ee401d5 virtio-devices: balloon: Fix typo, comment, and dead variants
Fix "Fail tp signal" typo, replace copy-pasted "entropy" comment
with "balloon", and remove EventFdWriteFail and QueueIterator error
variants that have no call sites.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
5702e9751c virtio-devices: rng: Preserve byte count on mid-chain errors
When address translation or the entropy read fails mid-chain the
handler reset total_len to zero before breaking out of the loop.
If earlier descriptors in the same chain were already filled, the
used ring entry under-reports the bytes actually written to guest
memory. Drop the reset so the used length reflects reality.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
aee4fd6010 virtio-devices: rng: Skip device-readable descriptors
A device-readable descriptor anywhere in the chain caused the handler
to reset the byte count to zero and abandon the rest of the chain.
This discards valid device-writable descriptors that follow and
misreports bytes already written to earlier descriptors.

Skip device-readable and zero-length descriptors individually so
the remaining device-writable buffers still get filled with entropy.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-6
2026-05-07 16:27:18 +00:00
Rob Bradford
f0bb79a3a9 build: Temporarily make VFIO job non-blocking
Don't block the CI passing if the VFIO integration tests fail.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-07 16:07:44 +01:00
Rob Bradford
05fd26b601 tests: Make ovs setup idempotent
Try and delete the bridge if it exists before setting up for the test.
This prevents cascading failures where if the test fails once any
subsequent run of the test will fail during the setup.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-07 16:07:00 +01:00
Keith Adler
1a74c666e5 vmm: trim qualified paths in uffd
Import common std types used by the userfaultfd wrapper instead of
spelling the full paths at every use site.

Signed-off-by: Keith Adler <kadler@cloudflare.com>
2026-05-07 08:52:42 +01:00
Keith Adler
3db4c0f7b4 tests: remove redundant hotplug sleep
The snapshot/restore hotplug path already waits for the exact
device-removed event through the event monitor. Drop the fixed sleep
before that poll so the test advances as soon as the event arrives.

Signed-off-by: Keith Adler <kadler@cloudflare.com>
2026-05-06 20:49:56 +00:00
Rob Bradford
8e7be74680 tests: Bump vfio integration test time to 25 minutes
This timeout is being reached and kicking jobs out of the MQ.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-06 23:03:40 +01:00
Anatol Belski
35609ff777 net_util: queue_pair: Surface malformed descriptors on the used ring
Several error paths in process_desc_chain returned the error before
calling queue.add_used for the offending descriptor. The affected
variants were DescriptorChainInvalid, DescriptorChainTooShort,
DescriptorInvalidHeader, and the GuestMemory variants raised during
descriptor chain translation, slice retrieval, or the num_buffers
write on the RX side.

Without an entry in the used ring the head descriptor remained owned
by the device. A guest that kept submitting bad chains could deplete
the queue over time.

Mark the head descriptor used with length 0 before propagating the
error to the caller, so the ring stays consistent regardless of how
the device decides to react to a guest induced failure.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-06 21:45:18 +01:00
Anatol Belski
945e1f2654 net_util: queue_pair: Handle RX short read gracefully
When readv returns fewer bytes than vnet_hdr_len the frame is
truncated. Report the truncated length to the guest in the used
ring instead of returning a fatal InvalidVirtioNetHeader error.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-06 21:45:18 +01:00
Anatol Belski
c71a8ea8dd net_util: queue_pair: Handle RX readv EINVAL gracefully
When readv from the TAP returns EINVAL the guest posted a buffer
too small for the vnet_hdr. Return len 0 to the used ring and
continue instead of killing the worker thread. Also move
go_to_previous_position into the appropriate error branches only.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-06 21:45:18 +01:00
Anatol Belski
02ea839838 net_util: queue_pair: Handle TX short tap write gracefully
When writev returns fewer bytes than vnet_hdr_len the packet is
truncated. Log the error and drop it instead of returning a fatal
InvalidVirtioNetHeader error that would crash the worker thread.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-06 21:45:18 +01:00
Anatol Belski
17fefa1e45 net_util: queue_pair: Handle TX writev EINVAL gracefully
When writev to the TAP returns EINVAL the guest submitted a
malformed packet. Drop it and continue instead of killing the
worker thread.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-06 21:45:18 +01:00
Anatol Belski
3402bc0762 virtio-devices: NEEDS_RESET on worker thread Err
Worker threads spawned through spawn_virtio_thread previously wrote to
exit_evt on any clean Err return, taking the whole VMM down on a single
failed device worker. A guest induced fault in any virtio device thus
propagated into a host wide failure.

Route the Err return through the shared mark_device_needs_reset helper
instead. The helper sets the DEVICE_NEEDS_RESET bit on device_status
and triggers a config change interrupt, so the device goes idle and
the guest is informed. The thread exits cleanly without killing the
rest of the VMM.

The panic and the seccomp filter apply paths keep writing to exit_evt.
A panicked worker may have left poisoned locks or partially mutated
state, so a hard exit remains the right policy there.

spawn_virtio_thread now takes the device_status and the interrupt
callback. Every native virtio and vhost-user call site is updated to
pass them in.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-06 21:45:18 +01:00
Anatol Belski
22856fffdd virtio-devices: Factor out NEEDS_RESET helpers
Both block and net implement the same DEVICE_NEEDS_RESET bookkeeping
when a corrupted virtqueue request is detected. They set the bit,
trigger a config change interrupt and log a warning. Move that logic
into shared device_needs_reset and mark_device_needs_reset helpers in
lib.rs and update both call sites to use them.

No functional change.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-06 21:45:18 +01:00
Damian Barabonkov
4eb1717fb0 tests: Add VFIO mmap BAR exclusion coverage
Exercise the new VFIO BAR exclusion option with NVIDIA
passthrough tests so the integration suite checks that selected
BARs are skipped.

The tests cover both legacy VFIO and iommufd paths while
preserving the existing hardware availability guards.

Signed-off-by: Damian Barabonkov <dbctl@pm.me>
Assisted-by: OpenCode:gpt-5.5
2026-05-06 14:15:41 +00:00
Damian Barabonkov
a858a1f115 docs: Document VFIO mmap BAR exclusion
Explain how operators can exclude selected VFIO BARs from mmap
when they know their workloads do not require MMIO access to those
regions.

Documenting the option separately keeps the new device argument
discoverable and calls out the valid BAR index range.

Signed-off-by: Damian Barabonkov <dbctl@pm.me>
Assisted-by: OpenCode:gpt-5.5
2026-05-06 14:15:41 +00:00
Damian Barabonkov
41e12e9d6a vmm, pci, openapi: Add VFIO mmap BAR exclusion
Allow VFIO devices to list BAR indices that should not be
mmapped into the guest. This lets operators skip large BARs that
are known not to be used by their workload.

When a BAR is skipped, the log also calls out that P2P DMA
mapping is skipped because the VFIO DMA map path uses the same
mmap backing.

Signed-off-by: Damian Barabonkov <dbctl@pm.me>
Assisted-by: OpenCode:gpt-5.5
2026-05-06 14:15:41 +00:00
Rob Bradford
12f48700a2 tests: Wait for boot notification from test_vfio_user L2 guest
Reuse the boot notification method we have for the L1 guests for the L2
guest. This removes the need to use SSH based boot tracking for
connecfting to the L2 guest and should make the test more reliable.

This requires making the L2 guest use a different cloud-init
configuration to the L1.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-06 12:30:08 +00:00
Rob Bradford
9703971ae7 tests: Use separate IP address for guest -> host notification
This will allow an L2 guest to notify the L1 host.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-06 12:30:08 +00:00
Muminul Islam
ad03b1b250 performance-metrics: Use default_cpus() across all tests
Replace explicit --cpus boot=N arguments with .default_cpus()
in all performance test functions. For confidential guests,
nested=on is not supported, so using default_cpus() ensures
nested=off is correctly set for confidential VMs.

The net throughput, net latency, and block I/O tests now set
guest.num_cpu to the number of queues before calling
default_cpus(), ensuring the correct vCPU count is used.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-06 11:40:42 +00:00
Muminul Islam
a5adc7014c performance-metrics: Pass full control to guest constructor
Refactor performance_test_new_guest to accept the full
PerformanceTestControl reference instead of just GuestVmType.
This allows the function to also set guest.num_cpu from
control.num_boot_vcpus, ensuring the guest is configured
with the correct number of vCPUs for the test.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-06 11:40:42 +00:00
Muminul Islam
d56c3ba320 scripts: Support confidential VM in run_metrics.sh
Use the VM_TYPE environment variable to conditionally enable
igvm and sev_snp features when building for confidential VMs.
Pass --vm-type confidential to the performance-metrics binary
so it can select the appropriate test configuration.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-06 11:40:42 +00:00
Muminul Islam
9f64400943 scripts: Add --vm-type argument to dev_cli.sh
Add a --vm-type argument to the tests command in dev_cli.sh to
allow specifying the type of VM (regular or confidential). The
value is passed to the container via the VM_TYPE environment
variable.

Also extract a prepare_igvm_files() helper function to reduce
duplication when copying IGVM files for confidential VM tests,
and call it from both integration_cvm and metrics paths.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-06 11:40:42 +00:00
Rob Bradford
eb8b10add0 virtio-devices: iommu: Cap number of domains per device
The per-domain mapping cap bounds memory inside one domain, but a
guest can still grow the domains map indefinitely with ATTACH
requests for distinct domain IDs. Reject ATTACH with
VIRTIO_IOMMU_S_NOMEM at 64K domains.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
63a523b3f3 virtio-devices: iommu: Clamp bypass config field to 0 or 1
The virtio spec says the device must never present a value other
than 0 or 1 for bypass. Mask off the upper bits on write so a
later read does not return whatever the driver wrote.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
efcc090811 virtio-devices: iommu: Enforce input_range on MAP requests
The device offers VIRTIO_IOMMU_F_INPUT_RANGE when the address
width is constrained, but never validates that guest MAP requests
fall inside the advertised range. The virtio spec requires such
requests to fail with VIRTIO_IOMMU_S_RANGE.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
1376f6e9ef virtio-devices: iommu: Return NOENT for UNMAP on unknown domain
The virtio spec mandates VIRTIO_IOMMU_S_NOENT, not S_INVAL, when
the target domain does not exist.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
f74255f5e8 virtio-devices: iommu: Tighten MAP request validation
The handler accepted unknown flag bits, unaligned ranges, and
overlapping mappings, and returned the wrong status code when the
target domain did not exist. The virtio spec requires explicit
rejections for each of these.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
024188cb7f virtio-devices: iommu: Validate ATTACH reserved field and flags
The virtio spec requires the device to reject ATTACH with a
non-zero reserved field, an unknown flag bit, or a bypass flag
that conflicts with an existing domain. The current handler
silently accepts all three.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
8fb4aa61c7 virtio-devices: iommu: Drop unrecognised requests without writing reply
The virtio spec requires the device to leave the reply buffer
untouched and report a used length of zero for an unrecognised
request type, so the driver can tell the request was not handled.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
59f9c7c08a virtio-devices: iommu: Cap mappings per domain
Domain::mappings only shrinks on UNMAP. Without a bound a guest can
issue MAP for arbitrarily many distinct virt_start values and drive
the VMM heap until the host runs out.

Reject MAP with VIRTIO_IOMMU_S_NOMEM at 1M entries per domain.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
f77f168532 virtio-devices: iommu: Roll back partial MAP and fix DETACH panic
A concurrent DETACH between the read-lock check and the write-lock
get_mut().unwrap() in MAP/UNMAP would panic the worker. Replace the
unwrap with a let-else.

A failure on a later endpoint in the per-endpoint MAP loop, or a
DETACH that races the missing-domain branch, must roll back the
external mappings already installed; otherwise domain.mappings
diverges from VFIO state.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
4d6c7c95c0 virtio-devices: iommu: Reject UNMAP that partially overlaps a mapping
An UNMAP that would split an existing mapping must be rejected with
VIRTIO_IOMMU_S_RANGE without removing anything. The previous start-only
retain silently left mappings that started outside the unmap range but
overlapped it.

Walk bookkeeping under a read lock and reject before touching VFIO so a
rejection cannot leave VFIO and bookkeeping out of sync.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
7989e46f8b virtio-devices: iommu: Pass size to translate_gva/translate_gpa
The DmaRemapping translate_gva and translate_gpa entry points discarded
the size argument that AccessPlatform's signature already carries and
only checked the base address. A buffer beginning inside a mapping but
extending past it was treated as fully translated, allowing reads or
writes outside the IOMMU-authorized window.

Add `size` to the trait, validate the full span fits in a single
mapping, and propagate it through AccessPlatformMapping.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Rob Bradford
f9abcb8d9c virtio-devices: iommu: Use checked add for reply length
The reply length was `hdr_len + size_of::<tail>()`, computed twice.
Make it explicit via checked_add and reuse the result.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-05-06 08:58:03 +00:00
Muminul Islam
d552320f4f docs: remove CI workflow in the testing doc
Remove CI workflow description section as the yaml
files change often and the docs become stale quickly.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-05 21:24:59 +00:00
Demi Marie Obenour
fb9532f857 virtio-devices: PCI: Hard-code the settings BAR number
It will always be 0.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-05-05 11:45:44 +00:00
Demi Marie Obenour
8b86dd9fb5 pci: Reduce use of magic number 4
No functional change intended.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-05-05 11:45:44 +00:00
Dylan Reid
68ae56eb74 vmm: memory_manager: test: build sparse fixtures via PUNCH_HOLE
The unit tests added in bf3279f09 built sparse files by writing only
at one offset and assuming the surrounding pages stayed unallocated.
That breaks on shmem/tmpfs with huge=within_size: kernel 6.10+ added
large-folio support to shmem, and on first write the kernel allocates
one folio whose order is the largest power-of-two number of pages
that fits inside the file size (capped at PMD-size). For a 64 KiB
test file the very first pwrite anywhere allocates a 64 KiB folio
covering the whole file, so SEEK_HOLE never reports a hole and
written_pages_show_as_data_extents,
sparse_file_yields_extents_at_written_positions, and
single_extent_at_zero_offset all fail. memfd_create lives on shmem
too and inherits the same THP policy from
/sys/kernel/mm/transparent_hugepage/shmem_enabled, so the problem is
not /tmp-specific.

Fix the fixtures, not the production code: build each test file via
a new sparse_layout() helper that writes the requested data extents
and then fallocate(FALLOC_FL_PUNCH_HOLE)s every gap. PUNCH_HOLE is
the explicit "deallocate these pages" syscall and is honored by every
Linux filesystem we run tests on (tmpfs, ext4, xfs, btrfs); the
kernel splits any large folio overlapping the punched range. The
resulting SEEK_DATA/SEEK_HOLE map matches the spec exactly regardless
of folio/THP policy.

For single_extent_at_zero_offset the dst side still loses to the
folio allocator -- writing 8 KiB into a 64 KiB tmpfs file allocates
a 64 KiB folio whether we want it or not -- so the previous
meta.blocks()-based sparseness assertion (which tested the filesystem,
not our code) is replaced with a sentinel pre-fill: dst starts filled
with 0xFE and the post-condition is that bytes outside the
source-data extent are still 0xFE. That directly verifies
write_region_sparse only touched the data extent without depending on
dst-side hole reporting.

Side effect: extent_at_non_zero_src_offset,
two_regions_in_same_destination_file_at_dst_offset, and
round_trip_sparse_write_then_read previously passed by accident on
hosts with mTHP-on-shmem -- their src memfds reported the whole file
as data so write_region_sparse silently fell into a dense copy of
zeros + data. With sparse_layout() the sources are genuinely sparse
and those tests now exercise the sparse path on every host.

Tested on tmpfs (huge=within_size) and ext4 (TMPDIR=/var/tmp); all 9
tests pass on both with no skips.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-05 08:42:56 +00:00
Anatol Belski
6daa9e14e6 virtio-devices: Test config vector OOB does not panic
Verify that firing a config change interrupt with msix_config
vector beyond the table size returns Ok without panicking.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-05 08:24:19 +00:00
Anatol Belski
6be080c083 virtio-devices: Test trigger with valid vector fires interrupt
Verify that a valid in bounds vector with MSI-X enabled
successfully triggers the interrupt source group.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-05 08:24:19 +00:00
Anatol Belski
a682112851 virtio-devices: Test notifier with OOB vector returns None
Verify that requesting a notifier with an out-of-bounds MSI-X
vector returns None instead of panicking.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-05 08:24:19 +00:00
Anatol Belski
088b1363e7 virtio-devices: Test trigger with NO_VECTOR returns Ok
Verify that triggering an interrupt when the vector is set to
VIRTQ_MSI_NO_VECTOR short-circuits and returns Ok.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-05 08:24:19 +00:00
Anatol Belski
442e7f85fa virtio-devices: Test trigger with OOB MSI-X vector does not panic
Verify that firing an interrupt with a queue vector beyond the
MSI-X table size returns Ok without panicking.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-05 08:24:19 +00:00
Anatol Belski
3b431afba4 virtio-devices: Check MSI-X vector bounds before table access
A malicious or buggy guest can write an out-of-bounds value to
queue_msix_vector or msix_config. When the device later triggers
an interrupt, it indexes into table_entries with the unchecked
vector, causing a panic.

Validate the vector against the MSI-X table size in both trigger()
and notifier() paths, logging a warning and returning early when
the vector exceeds the table bounds.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-05 08:24:19 +00:00
Dylan Reid
376434a695 block: bounds-check the cumulative descriptor length
Request::execute and Request::execute_async checked each data descriptor
against `disk_nsectors` using the request's fixed start sector. With
sector = disk_nsectors-1 and N descriptors of 512 bytes each, every
descriptor passed (top = disk_nsectors) but the vectored I/O
collectively read/wrote N*512 bytes starting at the last sector — N-1
sectors past EOF.

For the io_uring/aio raw backends this lets the guest extend the host
disk image beyond its provisioned size, exhausting the host filesystem.
For fixed-VHD images (footer at end of file) the same chain overwrites
the footer with guest-controlled bytes, corrupting the disk image.

Replace the per-descriptor check with a chain-wide check_data_bounds().
Pre-validating the entire request before beginning the operation avoids
having to unroll a partial submit.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-05 08:21:03 +00:00
Muminul Islam
3df0579866 performance-metrics: avoid double ref in test selection
Use into_iter() for test_list when building tests_to_run.

This keeps the collected type as Vec<&PerformanceTest>.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-04 21:36:25 +00:00
Rob Bradford
63d6beb170 virtio-devices: vsock: Adapt used handling to match other devices
Follow the same pattern as other virtio devices using a bool to check if
it needs notification and propagating its own Error enum.

Sadly this does still use `anyhow!()` but this does match with the
behaviour of the other devices in their implementations.

As a side effect we can now remove two errors from the top-level Error
enum in virtio-devices as these were only used by this module and those
errors had mangled descriptions.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-04 21:28:54 +00:00
Rob Bradford
7d8986aad0 tests: Allow more time for firmware & O_DIRECT tests
Booting the VM on these tests takes longer so allow longer before
timing out the boot.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-04 21:28:33 +00:00
Rob Bradford
a93dbe790f tests: Cleanup interfaces in test_vfio
If this test flakes is can then cause subsequent invocations to fail as
the test has left its special test interfaces alive.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-04 21:28:33 +00:00
Rob Bradford
f09ab0898d vmm: config: Reject rate limiting with vhost_user
Rate limiting is implemented in the virtio device layer and does not
apply to vhost-user devices which delegate I/O handling to an external
process.

Add validation to reject configurations where vhost_user is enabled
along with rate limiting options (bw_size, ops_size, or
rate_limit_group) for both disk and network devices.

This prevents users from mistakenly configuring rate limiting that would
be silently ignored when using vhost-user backends.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-04 17:43:25 +00:00
Anatol Belski
7fbc5a1354 pci: msix: Replace panic with graceful error on invalid table write
A malicious or buggy guest can issue an MSI-X table write with an
unexpected size (not 4 or 8 bytes), triggering an assert!() that
crashes the VMM process. Replace the assertion with an error log and
early return to maintain VMM stability under adversarial guest
behavior.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-04 16:08:06 +01:00
Nguyen Dinh Phi
c5951252a5 tests: Adding integration tests for migration of paused VM
Adding a paused flag to live_migration() tests; when this
flag is set, the VM will be paused before migration is
performed.

Signed-off-by: Nguyen Dinh Phi <phind.uet@gmail.com>
2026-05-04 09:30:30 +00:00
Nguyen Dinh Phi
23fc9ca258 vmm: support migration of paused VMs
This extends migration to also support paused VMs, preserving the
paused state on the destination.

Changes:
- Add CompletePaused protocol command that finalizes migration without
 resuming the VM on the destination
- Skip the pause step during migration if the VM is already paused
- On migration failure, only restore the running state if
  the VM was originally running (not paused)

Signed-off-by: Nguyen Dinh Phi <phind.uet@gmail.com>
2026-05-04 09:30:30 +00:00
Dylan Reid
e2b9fa261b virtio-devices: get_host_address_range check fixes
`get_host_address_range` used `check_range(addr, size)` as a guard then
unwrapped `get_slice(addr, size)`. This allowed a span across two
regions to hit the unwrap (get_slice limits to one range).

If `size` were zero, then the checks were all skipped. Causing a panic
later on for an invalid address.

Make get_slice the sole authority and reject size==0 explicitly.
Callers already handle None.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-04 09:08:43 +00:00
Dylan Reid
c565d4eb88 virtio-devices: block: don't kill worker on per-request errors
The guest can cause submit and completion failures with malformed chains
or invalid addresses. However, this shouldn't permanently stall the
device and terminate the worker.

Genuine reset-worthy failures set needs_reset and return `Ok` anyways
and will more cleanly reset the worker.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-04 09:08:43 +00:00
Dylan Reid
fbcf2fd6b0 virtio-devices: block: cap submit-loop iterations to virtqueue size
process_queue_submit's drain loop builds a fresh queue.iter() per
iteration, which re-reads the guest avail index on every call and has
no per-call cap (the per-iter gap check in virtio-queue only protects
against avail_idx jumping more than queue_size between two reads).
In theory, a malicous or buggy guest could keep adding descriptors and
cause this loop to overflow the iouring submit queue.

Cap a single drain at queue_size. A spec-compliant driver never
produces more than queue_size outstanding entries simultaneously, so
the cap is invisible to well-behaved guests.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-05-04 09:08:43 +00:00
Muminul Islam
48e671011c scripts: Add kernel option validation in prepare_linux
Add validation checks to prepare_linux() to catch invalid
kernel option combinations early:

- Error if --build-guest-kernel and CH_CUSTOM_KERNEL are
  both provided, as they are mutually exclusive.
- On x86_64, error if only one of CH_CUSTOM_KERNEL or
  CH_CUSTOM_BZIMAGE is set; both must be provided together.
- Fix kernel-already-present check: use per-architecture
  branches with correct bash syntax (elif instead of
  else-if, [[ ]] instead of [ && ]) so aarch64 and x86_64
  are each handled properly.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-04 08:26:15 +00:00
Muminul Islam
bb8bbb2961 docs: add testing documentation
Add docs/testing.md covering the dev_cli.sh interface, all
test types (unit, integration, VFIO, Windows, live migration,
rate limiter, CVM), custom kernel/firmware overrides via
environment variables, performance metrics, code coverage,
and the CI workflow matrix.

Assisted-by: GitHub Copilot:Claude-Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-04 08:26:15 +00:00
Muminul Islam
c5e90a37e6 scripts: skip downloads when workloads already exist
Add file-existence guards around firmware and OVMF download
calls in integration test scripts that were missing them.
Also guard prepare_linux() in test-util.sh so it returns
early when the kernel binary is already present.

This lets users pre-populate the workloads directory (e.g.
via CH_CUSTOM_KERNEL, CH_CUSTOM_FIRMWARE, CH_CUSTOM_OVMF)
and avoid redundant network fetches or source builds inside
the container.

Updated scripts:
- test-util.sh (prepare_linux early return)
- run_integration_tests_aarch64.sh
- run_integration_tests_vfio.sh
- run_integration_tests_windows_x86_64.sh
- run_integration_tests_windows_aarch64.sh

Assisted-by: GitHub Copilot:Claude-Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-04 08:26:15 +00:00
Muminul Islam
00bee62653 scripts: skip checksum for custom-provided workloads
Skip sha1sum verification for firmware files that were
provided via CH_CUSTOM_FIRMWARE or CH_CUSTOM_OVMF environment
variables.  Custom files will not match the expected checksums,
so we filter them out of the sha1sums list before running
sha1sum --check.

Updated scripts:
- run_integration_tests_x86_64.sh
- run_integration_tests_aarch64.sh

Assisted-by: GitHub Copilot:Claude-Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-04 08:26:15 +00:00
Muminul Islam
38bc97dd4d scripts: copy custom workloads before container launch
When CH_CUSTOM_KERNEL, CH_CUSTOM_FIRMWARE, or CH_CUSTOM_OVMF
environment variables are set, copy the referenced files into
the host workloads directory before starting Docker. The files
land at the default paths the test scripts expect
(vmlinux-x86_64, Image-arm64, hypervisor-fw, CLOUDHV.fd,
CLOUDHV_EFI.fd), so the existing download-if-missing guards
inside the container skip the network fetch.

Each variable is independent; users can override any
combination without affecting the others.

Assisted-by: GitHub Copilot:Claude-Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-04 08:26:15 +00:00
Muminul Islam
339d0a84f9 scripts: deduplicate container env args in dev_cli.sh
Extract a common_env_args bash array with the environment
variables shared by both unit and integration test containers
(BUILD_TARGET, RUSTFLAGS, TARGET_CC). The unit test block
uses common_env_args plus its own LLVM_PROFILE_FILE. After
the unit block, common_env_args is extended with the USER
and AUTH_DOWNLOAD_TOKEN variables used by all integration
test groups.

Assisted-by: GitHub Copilot:Claude-Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-04 08:26:15 +00:00
Muminul Islam
afeeeee494 scripts: deduplicate container runtime args in dev_cli.sh
Extract a common_args bash array with the runtime arguments
shared by both unit and integration test containers (name,
workdir, rm, seccomp, volumes). The unit test block uses
common_args plus its own device and cap-add flags. After
the unit block, common_args is extended with the privileged,
ipc, net, tmpfs, and workload-volume flags used by all
integration test groups.

Assisted-by: GitHub Copilot:Claude-Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-04 08:26:15 +00:00
Rob Bradford
80c56f728e vmm: config: Validate mergeable and shared are not both set
KSM will not attempt to merge pages that are mapped as MAP_SHARED, so
configuring memory with both mergeable and shared options is invalid.
Add validation to reject configurations where both options are enabled
for memory or memory zones.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-04 08:24:26 +00:00
Julian Schindel
9a0fb1b06a vmm: fix UB in load_igvm(...)
With the `mshv` feature enabled, the immutable `data` `Vec` is mutated
via a pointer. This violates Rust aliasing rules. Fixed by cloning the
`Vec` to a mutable instance when the `mshv` feature is active.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-04 08:22:29 +00:00
Julian Schindel
5f360abdc7 arch: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
2b6e9df4e3 block: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
7455ff1ea4 devices: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
9b0d4b20ea hypervisor: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
2a6b746f5e net_util: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
6de472f1bb pci: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
ae7113e1d4 virtio-devices: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
8b101fb890 vmm: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
cb09c37c55 vm-migration: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
8abd9d7db5 tpm: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Julian Schindel
032f29da29 misc: replace as <pointer> casts with safer alternatives
`as` casts can change mutability, which quickly leads to undefined
behavior.

Signed-off-by: Julian Schindel <mail@arctic-alpaca.de>
2026-05-03 08:38:31 +00:00
Ruben Hakobyan
c97d635d40 hypervisor, vmm: Build and pass SNP ID block to launch finish
Add KvmSevSnpIdBlock and KvmSevSnpIdAuth structs matching the AMD
SEV-SNP Firmware ABI Spec (Rev 1.58), and build them from the IGVM
SNP ID block directive during launch finish. This properly populates
id_block_uaddr/id_auth_uaddr in KVM_SEV_SNP_LAUNCH_FINISH and derives
auth_key_en from the assembled author key, matching QEMU's behavior.

Thread the guest policy from sev_snp_init to launch_finish via an
atomic on KvmVm so the ID block gets the correct policy value.

Also track has_snp_id_block in IgvmLoadedInfo to enable the ID block
based on whether the IGVM file actually contains one, rather than
hardcoding it for KVM.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-05-01 20:28:55 +00:00
Ruben Hakobyan
bfab43e252 vmm: Parse guest policy from IGVM initialization headers
Extract the SNP guest policy from IGVM initialization headers when
available, falling back to the default policy. This matches QEMU's
behaviour where only a non-zero IGVM policy overrides the default.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-05-01 20:28:55 +00:00
Kevin Hui
4a91b4a608 vmm: Preserve SEV-SNP IGVM load ordering
Preserve the original IGVM import order for KVM SNP launch updates.
The launch digest is order-sensitive, so only coalesce adjacent pages
that already share the same page type and size. MSHV continues to
sort by GPA for hypercall batching.

Signed-off-by: Kevin Hui <kevinhui@meta.com>
2026-05-01 20:28:55 +00:00
Kevin Hui
982934fba0 vmm: Add SNP zero-page type for IGVM imports
Introduce the KVM_SNP_PAGE_TYPE_ZERO page type for ZERO
pages. AMD SEV SNP can accept ZERO pages as a page in which
the page memory is functionally just zeroes

Signed-off-by: Kevin Hui <kevinhui@meta.com>
2026-05-01 20:28:55 +00:00
Kevin Hui
e3e22d8e78 vmm: Add unit tests for generating hash blocks for SEV-SNP
Verify the SEV hash table layout, GUID placement, kernel/initrd/cmdline
digest values, and the setup_sects > boot_params size branch. These
guard against silent regressions in the launch digest computation.

Signed-off-by: Kevin Hui <kevinhui@meta.com>
2026-05-01 20:28:55 +00:00
Kevin Hui
70388fb1bb vmm: Introduce kernel hashes measured boot
This introduces the kernel hashes measured boot table into
cloud hypervisor if a cmdline and kernel is passed into an
SEV-SNP CVM, incorporating a kernel/cmdline/optional initrd
into a memory page that is measured into the launch digest
of a SEV-SNP CVM. If both --kernel and --cmdline are not
provided, we do not insert this data page

Signed-off-by: Kevin Hui <kevinhui@meta.com>
2026-05-01 20:28:55 +00:00
Ruben Hakobyan
9f1247fe60 devices: fw_cfg: Don't modify kernel header for KVM SEV-SNP guests
For KVM SEV-SNP guests, the VMM should not modify the kernel
boot header before sending it via fw_cfg. The guest firmware is expected
to set fields like type_of_loader itself.

For upcoming measured boot logic for SEV-SNP, modifying `type_of_loader`
causes the kernel hash computed by the VMM to diverge from the hash that
`sev-snp-measure` (and the guest firmware) compute, resulting in a
launch measurement mismatch.

This matches QEMU's behavior, which skips kernel header modifications
for confidential guests so the data sent via fw_cfg matches the
original kernel file provided by the user.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-05-01 20:28:55 +00:00
Rob Bradford
ffeee2880f vmm: memory_manager: Handle sparse snapshot file on restore
Walk the input snapshot file extent by extent via lseek(SEEK_DATA) /
lseek(SEEK_HOLE) within each region's slot and read only those bytes
into guest RAM via the existing read_volatile_from primitive. Holes are
left as the guest mapping's natural zero-fill, which matches the source
content.

Symmetric counterpart to sparse-write on snapshot. Works for both new
sparse snapshots and old dense snapshots: a dense file has no holes, so
SEEK_DATA returns the full range as one extent and the I/O pattern
matches the previous behaviour.

If the input file's filesystem does not support SEEK_HOLE the code falls
back to the existing dense read path.

Measured on a 4 GiB shared-memory VM (2 vCPUs, ~340 MiB touched), total
restore time as reported by the in-tree timing instrumentation:

  Before (dense):  ~1487ms, reads 4.0 GiB from file
  After (sparse):  ~136ms,  reads 340 MiB from file (92% less I/O, 11x faster)

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-01 15:40:50 +00:00
Rob Bradford
ee1d9dae34 vmm: memory_manager: Write snapshot file sparsely
For memfd-backed guest RAM regions, walk the backing fd extent by extent
via lseek(SEEK_DATA) / lseek(SEEK_HOLE) and write each populated extent
into the snapshot file's per-region slot via
std::os::unix::fs::FileExt::write_at. Pre-size the file with
set_len(total): on filesystems that support sparse files unwritten bytes
become real holes; on others the kernel zero-fills the allocation, which
is still byte-correct.

If set_len fails (some FUSE backends reject ftruncate-extend with
EOPNOTSUPP), fall back entirely to the dense write path which streams
bytes sequentially via write_volatile_to and never writes past the
growing EOF.

When the guest region has no backing file (anonymous mmap) or the
backing fd does not support SEEK_HOLE (hugetlbfs), fall back to the
dense write path on a per-region basis.

The on-disk byte stream is identical to the dense format from the
perspective of any reader using read/pread/mmap, so old readers see no
change.

Measured on a 4 GiB shared-memory VM (2 vCPUs, ~340 MiB touched), total
snapshot time as reported by the in-tree timing instrumentation:

  Before (dense):  ~2400ms, 4.0 GiB on disk
  After (sparse):  ~132ms,  340 MiB on disk (92% smaller, 18x faster)

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-01 15:40:50 +00:00
Rob Bradford
bf3279f094 vmm: memory_manager: Add SEEK_DATA-based extent iterator
Adds next_data_extent: a streaming helper that returns the next
populated extent within a window of a file descriptor using
lseek(SEEK_DATA) / lseek(SEEK_HOLE). Used by subsequent commits to walk
the snapshot file extent-by-extent without collecting the full extent
list.

Returns an error on fds or filesystems without SEEK_HOLE support so the
caller can fall back to a dense write path.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-01 15:40:50 +00:00
Rob Bradford
739ea8c9fc ci: Remove lychee as a required CI step
Remove the dependency on lychee from all-green.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-05-01 12:14:43 +01:00
Muminul Islam
89cae74ddd performance-metrics: Use default_kernel_cmdline helper
Replace explicit --kernel and --cmdline arguments with the
default_kernel_cmdline() helper in performance_net_throughput,
performance_net_latency, and performance_block_io. This
simplifies the code and ensures consistency with how the
kernel command line is configured across tests.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
3440802c99 performance-metrics: CVM not supported on AArch64
Confidential VMs (CVM) are not currently supported on the
AArch64 architecture. Add an early check in the performance
metrics binary to exit with a clear error message when CVM
mode is selected on AArch64.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
7d684e3add performance-metrics: Add --vm-type CLI argument
Add a --vm-type command-line argument to allow users to select
between 'regular' (default) and 'confidential' (CVM) VM types
when running performance tests.

Example: --vm-type confidential

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
ab7749a5ce performance-metrics: Apply vm_type override in run()
Apply the vm_type override from PerformanceTestOverrides to the
effective_control used during test execution, alongside the
existing test_timeout override.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
56a986145e performance-metrics: Add vm_type to PerformanceTestOverrides
Add an optional vm_type field to PerformanceTestOverrides to
allow overriding the VM type at runtime. Include vm_type in
the Display output for override logging.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
55e2700fbc performance-metrics: Use control.vm_type in all tests
Replace hardcoded GuestVmType::Regular with control.vm_type
in all performance test functions to support CVM benchmarking:
net_throughput, net_latency, boot_time, boot_time_pmem,
block_io, and restore_latency.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
938db39e26 performance-metrics: Add vm_type param to new_guest
Update performance_test_new_guest() to accept a GuestVmType
parameter. When set to Confidential, configure the guest with
CVM-specific settings: vm_type, boot_timeout, and nested
virtualization disabled.

All callers pass GuestVmType::Regular to preserve existing
behavior.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
82a0dc7252 performance-metrics: Add vm_type to PerformanceTestControl
Add a vm_type field of type GuestVmType to PerformanceTestControl,
defaulting to GuestVmType::Regular. Include vm_type in the Display
output for test control logging.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
4b4845eb4d performance-metrics: Refactor run() to use effective_control
Consolidate override application into a single effective_control
variable built once before the test loop. This removes duplicated
timeout override logic from both warmup and measurement iterations.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
11623a2183 test_infra: Implement Display for GuestVmType
Add Display trait implementation for GuestVmType to enable
formatted output of the VM type in logs and diagnostics.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
0e7fcc623e test_infra: Implement FromStr for GuestVmType
Add FromStr trait implementation for GuestVmType to enable
parsing from CLI string arguments. Supports "regular" and
"confidential" string values.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 09:36:58 +00:00
Muminul Islam
246890802b build: update mshv-bindings/ioctls to 0.6.9
Update mshv-bindings and mshv-ioctls from 0.6.8 to 0.6.9
in workspace Cargo.toml and fuzz/Cargo.toml.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-05-01 08:52:44 +00:00
Julian Schindel
05d8606a32 vmm: replace unsafe with safe Vec creation for LocalX2Apic
The `LocalX2Apic` structs implements `IntoBytes`, so we can use the safe
abstraction instead having to use `unsafe`.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
2026-05-01 08:26:38 +00:00
dependabot[bot]
e0ab116a0c build(deps): bump the non-rust-vmm group across 2 directories with 11 updates
Bumps the non-rust-vmm group with 4 updates in the / directory: [cc](https://github.com/rust-lang/cc-rs), [jiff](https://github.com/BurntSushi/jiff), [pastey](https://github.com/as1100k/pastey) and [zbus_names](https://github.com/z-galaxy/zbus).
Bumps the non-rust-vmm group with 4 updates in the /fuzz directory: [libc](https://github.com/rust-lang/libc), [cc](https://github.com/rust-lang/cc-rs), [pastey](https://github.com/as1100k/pastey) and [winnow](https://github.com/winnow-rs/winnow).


Updates `cc` from 1.2.60 to 1.2.61
- [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.60...cc-v1.2.61)

Updates `jiff` from 0.2.23 to 0.2.24
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.23...jiff-static-0.2.24)

Updates `jiff-static` from 0.2.23 to 0.2.24
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.23...jiff-static-0.2.24)

Updates `pastey` from 0.2.1 to 0.2.2
- [Release notes](https://github.com/as1100k/pastey/releases)
- [Changelog](https://github.com/AS1100K/pastey/blob/master/CHANGELOG.md)
- [Commits](https://github.com/as1100k/pastey/compare/v0.2.1...v0.2.2)

Updates `zbus_names` from 4.3.1 to 4.3.2
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus_names-4.3.1...zbus_names-4.3.2)

Updates `zvariant` from 5.10.0 to 5.10.1
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant-5.10.0...zvariant-5.10.1)

Updates `zvariant_derive` from 5.10.0 to 5.10.1
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant_derive-5.10.0...zvariant_derive-5.10.1)

Updates `zvariant_utils` from 3.3.0 to 3.3.1
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant_utils-3.3.0...zvariant_utils-3.3.1)

Updates `libc` from 0.2.185 to 0.2.186
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.186/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.185...0.2.186)

Updates `bitfield-struct` from 0.12.1 to 0.13.0
- [Release notes](https://github.com/wrenger/bitfield-struct-rs/releases)
- [Commits](https://github.com/wrenger/bitfield-struct-rs/compare/0.12.1...0.13.0)

Updates `cc` from 1.2.60 to 1.2.61
- [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.60...cc-v1.2.61)

Updates `pastey` from 0.2.1 to 0.2.2
- [Release notes](https://github.com/as1100k/pastey/releases)
- [Changelog](https://github.com/AS1100K/pastey/blob/master/CHANGELOG.md)
- [Commits](https://github.com/as1100k/pastey/compare/v0.2.1...v0.2.2)

Updates `winnow` from 1.0.1 to 1.0.2
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.61
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.24
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.24
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: pastey
  dependency-version: 0.2.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zbus_names
  dependency-version: 4.3.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zvariant
  dependency-version: 5.10.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zvariant_derive
  dependency-version: 5.10.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zvariant_utils
  dependency-version: 3.3.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.186
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bitfield-struct
  dependency-version: 0.13.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.61
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: pastey
  dependency-version: 0.2.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: winnow
  dependency-version: 1.0.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-01 00:50:42 +00:00
dependabot[bot]
3bd90933e3 build(deps): bump crate-ci/typos from 1.45.2 to 1.46.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.45.2 to 1.46.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](7c57295821...bbaefadf97)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-01 00:37:26 +00:00
Rob Bradford
9411f7ecd8 vmm: Validate balloon size against total RAM
The total RAM in the system needs to consider any hotpluggable RAM that
is hotplugged in as well as the initial static "base" RAM.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-30 15:56:09 +00:00
Muminul Islam
f6ed896f68 vmm: gate reserve_bootloader_regions on KVM hypervisor type
The reserve_bootloader_regions() call allocates RAM regions at
KVM-specific addresses (0xffc00000 for stage0, 0xfffffffff000
for VMSA) that are only needed by the KVM SEV-SNP boot path.

The existing #[cfg(all(feature = "kvm", feature = "sev_snp"))]
compile-time guard is insufficient when both 'mshv' and 'kvm'
features are enabled in the same binary. The runtime check only
verified sev_snp_enabled() but not the hypervisor type, causing
these KVM-specific memory regions to be allocated on MSHV.

On MSHV, these spurious RAM mappings at high addresses interfere
with the hypervisor's address space layout. When the guest kernel
subsequently accesses MMIO regions (e.g., IOAPIC at 0xFEC00000),
MSHV incorrectly reports HVMSG_UNACCEPTED_GPA instead of routing
the access through MMIO emulation, crashing the guest.

Add a runtime hypervisor type check to ensure these regions are
only reserved when running on KVM.

Assisted-by: Claude:Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-30 10:39:28 +00:00
Muminul Islam
4608de134f vmm: igvm: gate CPUID page read with runtime hypervisor check
The SnpCpuidInfo allocation and guest_memory.read() call in the
isolated page import loop are only needed for KVM's CPUID page
retry logic. However, when building with both 'mshv' and 'kvm'
features, #[cfg(feature = "kvm")] alone is insufficient as a
guard because both features compile into the same binary.

Without a runtime hypervisor type check, this code executes on
MSHV as well, reading guest memory at arbitrary GPAs that may
not be valid in the MSHV memory layout. This can cause undefined
behavior or crashes during IGVM loading.

Add #[cfg(feature = "kvm")] to the variable declarations and
wrap the guest_memory.read() call in a runtime check for
HypervisorType::Kvm to ensure it only executes on KVM.

Assisted-by: Claude:Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-30 10:39:28 +00:00
Muminul Islam
7d24608bcf vmm: igvm: use correct MSHV page types for CPUID and secrets
The PageTypeConfig for MSHV incorrectly mapped the cpuid field
to HV_ISOLATED_PAGE_TYPE_NORMAL (0) and the secrets field to
HV_ISOLATED_PAGE_TYPE_UNMEASURED (3).

The correct MSHV page type constants are:
- CPUID pages: HV_ISOLATED_PAGE_TYPE_CPUID (5)
- Secrets pages: HV_ISOLATED_PAGE_TYPE_SECRETS (4)

This was introduced in commit 75ed2c9f90 ("vmm: add KVM
SEV-SNP support to IGVM loader") which abstracted page types
into a PageTypeConfig struct but assigned wrong values for the
MSHV variant. Using incorrect page types causes the MSHV
hypervisor to reject or mishandle isolated page imports,
leading to guest boot failure.

Assisted-by: Claude:Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-30 10:39:28 +00:00
Muminul Islam
8598b45a95 vmm: skip configure_system when rsdp_addr is None
For SEV-SNP guests using IGVM, the ACPI tables and system
configuration (MP tables, EBDA, SMBIOS, PVH start info, e820)
are provided by the IGVM file. The rsdp_addr is set to None
for these guests to indicate ACPI table creation was skipped.

Commit 7d65187350 ("vmm: make RSDP address optional in
configure_system") removed the guard that prevented calling
configure_system when rsdp_addr is None. This caused MSHV
SEV-SNP guests to crash because configure_system writes to
guest memory locations that conflict with the IGVM-provided
layout.

Restore the guard by only calling configure_system when
rsdp_addr is Some, which preserves the intended behavior
for CVM guests while still allowing the Option<GuestAddress>
refactoring.

Assisted-by: Claude:Opus-4.6

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-30 10:39:28 +00:00
Rob Bradford
87ffc620e4 virtio-devices: net: Gracefully handle MTU query failure
If querying the fd's MTU fails (because it was from a different network
namespace). Degrade gracefully by not advertising the VIRTIO_NET_F_MTU
feature and instead let the guest kernel use the default 1500 Ethernet
MTU.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-29 17:57:14 +00:00
Muminul Islam
475c8d3efb hypervisor: mshv: Use VP register page for state access
Use the VP register page to read and write emulation-related
special registers directly, avoiding expensive IOCTLs for
registers that instruction emulation never touches.

In cpu_state(), read only segments, cr0, and efer from the VP
register page instead of calling get_sregs() which issues
IOCTLs for tr, ldt, gdt, idt, cr2, apic_base, and
pending_interruption.

In update_cpu_state(), when segments change, write only the 6
segment registers to the VP register page and set the segment
dirty bit, instead of calling set_sregs() which issues IOCTLs
for tr, ldt, gdt, idt, cr0-cr4, cr8, efer, and apic_base.

Both paths fall back to the IOCTL-based methods when the VP
register page is not available.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-29 15:07:26 +00:00
Dylan Reid
54cde010c5 pci: vfio_user: bounds-check size in DMA map
VfioUserDmaMapping::map is reached from the virtio-iommu MAP handler
with (iova, gpa, size) all guest-controlled. Validate the length of the
region fits, not just the start.

Before this change the vfio-user on the other end could get a size that
spans past the end of its backing storage.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-29 09:23:36 +00:00
Dylan Reid
9f405a21ac virtio-devices: vsock: Add bounds check on inline TX path
The TX path's inline-data branch didn't check the inline buffer length
against the guest-supplied pkt.len() field. The worker will later panic
when it tries to index the packet.

Add the missing check, mirroring the other TX branches.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-29 09:23:36 +00:00
Dylan Reid
02b503ee16 virtio-devices: vdpa: checked arithmetic in dma_unmap
dma_unmap computed `iova + size - 1` unchecked while the sibling dma_map
already used checked_add/checked_sub. A guest reaching dma_unmap via
VIRTIO_IOMMU_T_UNMAP could cause a panic.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-29 09:23:36 +00:00
Dylan Reid
5b199498ff virtio-devices: iommu: checked arithmetic for MAP
Catch u64 overflows on map so that later translation requests from the
guest don't have a vector for causing a host panic.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-29 09:23:36 +00:00
Dylan Reid
22eeb3f808 virtio-devices: pci: skip activation of invalid queues
prepare_activator() called queue.is_valid() and only logged the failure,
then still pushed the queue to the activator. This would later panic.

Technically this is a fixup for:
a10508970 "virtio-devices: Support driver programming fewer queues"
But that's been in there since 2021. The intent was to allow a subset of
possible queues to be configured, but the invalid queues slipped through
too.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-29 09:23:36 +00:00
Stepan Rabotkin
feddff025a vmm: openapi: add user_devices to spec
Signed-off-by: Stepan Rabotkin <epicstyt@gmail.com>
2026-04-29 07:19:29 +00:00
Wei Liu
389257964b hypervisor: mshv: Make the translation caching code idiomatic
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-04-28 17:42:41 +00:00
dependabot[bot]
b03e186270 build(deps): bump crate-ci/typos from 1.45.1 to 1.45.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.45.1 to 1.45.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](cf5f1c29a8...7c57295821)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.45.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-28 03:15:02 +00:00
Rob Bradford
1bee4edf0c ci: Drop superseded workflows
Delete the PR/MQ workflows superseded by consolidated ci.yaml.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 20:31:44 +00:00
Rob Bradford
7e3129a303 ci: Consolidate PR/MQ workflows into a single ci.yaml
Replace existing CI files with a consolidated one to delay starting
resource intensive CI jobs until after less resource intensive ones have
completed: e.g. don't start integration tests if the basic build tests
fail.

Architecture:
- Trigger on [pull_request, merge_group]; concurrency keyed per PR/ref.
- preflight job classifies changed paths and exposes a 'full' flag that
  gates the heavier build/quality/integration suite.  Any CI change
  classifies itself as full so it is exercised.
- Leaf jobs gate at the job level using preflight outputs; doc-only,
  openapi-only, dockerfile-only and similar PRs skip the full suite.
- integration-x86-64-pr runs the (garm-jammy, gnu) slice on PR and MQ;
  integration-x86-64-mq runs the other 3 matrix entries on MQ only.
- integration-{arm64, vfio, windows, rate-limiter} are MQ-only.
- integration jobs gate on dco/quality/build success.
- A single all-green aggregator job is the only required-status check;
  it folds in every leaf job via `needs`.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 20:31:44 +00:00
Rob Bradford
8d62133383 virtio-devices: generic-vhost-user: Config change notification
Add support for backend that is connected via the vhost-user-generic
frontend to generate an interrupt into the guest when it has made a
change to the configuration. This is useful for devices that can change
the exposed configuration at runtime.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 11:51:43 +00:00
Anatol Belski
abef0e5b69 block: qcow: Add physical size test
Verify that a freshly created sparse QCOW2 image reports a
physical size smaller than its logical size.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-27 11:19:37 +00:00
Anatol Belski
5958872943 block: qcow: Add try_clone backend preservation tests
Verify that try_clone preserves the backend dispatch for both
sync and io_uring backends.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-27 11:19:37 +00:00
Anatol Belski
9043098473 block: qcow: Add async I/O dispatch tests
Verify that the sync backend disables batch requests and the
io_uring backend enables them.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-27 11:19:37 +00:00
Anatol Belski
4ec2ff1080 block: qcow: Add test for correct logical size
Verify that QcowDisk reports the expected virtual size.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-27 11:19:37 +00:00
Anatol Belski
587093ddfd block: qcow: Remove old wrapper structs, restrict visibility
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>
2026-04-27 11:19:37 +00:00
Anatol Belski
8a77feb813 block: qcow: Update existing tests to use QcowDisk
Replace QcowDiskSync and QcowDiskAsync constructors in the
qcow_sync and qcow_async test modules with QcowDisk::new,
passing use_io_uring=false and use_io_uring=true respectively.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-27 11:19:37 +00:00
Anatol Belski
1e4516fa5d block: qcow: Switch factory to QcowDisk
Update open_qcow2 to construct QcowDisk instead of choosing
between QcowDiskAsync and QcowDiskSync. The backend decision
is now made inside QcowDisk::create_async_io.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-27 11:19:37 +00:00
Anatol Belski
65c98ca157 performance-metrics: Use QcowDisk for QCOW2 benchmarks
Replace QcowDiskSync and QcowDiskAsync with QcowDisk in all QCOW2
benchmark helpers. The sync helpers pass use_io_uring=false, the
async helpers pass use_io_uring=true.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-27 11:19:37 +00:00
Anatol Belski
27ee36449c block: qcow: Add unified QcowDisk wrapper
Introduce QcowDisk, a unified DiskFile implementation for QCOW2
disk images that handles backend selection at runtime via a
use_io_uring flag, matching the pattern used by FixedVhdDisk.

The wrapper delegates to QcowSync or QcowAsync based on the flag
and includes a compile time guard that returns an error when
io_uring is requested but the feature is not enabled.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-27 11:19:37 +00:00
Rob Bradford
b59501e041 virtio-devices: pci_device: Remove Option<..> around VirtioInterrupt
The VirtioInterrupt is now always created so the Option<..> can always
be removed.

As a side effect the interrupt_source_group can also be removed from the
struct.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:15:37 +00:00
Rob Bradford
760610a432 virtio-devices: pci_device: Remove Option<..> from msix_config
Since this is always created there is no need to make it an Option type
simplifying the code. Historically it was an Option to support INTx
based virtio but that was removed long ago.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:15:37 +00:00
Rob Bradford
02b9b67fc2 virtio-devices: pci_device: Remove msix_num check
msix_num is guaranteed to be at least 1 so this check (and the Option)
that it returns can be removed.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:15:37 +00:00
Rob Bradford
85012fbe5c virtio-devices: Calculate number of msix interrupts in VirtioPciDevice
Rather than calculate in the DeviceManager and pass it through do it in
the device where it already has all the required information.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:15:37 +00:00
Rob Bradford
e11ff541da virtio-devices: Simplify interrupt handling
Previously the interrupt was created in VirtioPciDevice, moved via the
Option::take() to the VirtioPciDeviceActivator and then moved to the
VirtioDevice upon activation. On reset it would be moved back ready for
reactivation.

Since this already an Arc type remove the wrapping Option and instead
refcount it such that the VirtioPciDevice can continue to hold onto it
for later activations.

This significantly simplifies the reset() logic as there is no need to
hand back the interrupt.

A few devices used whether the interrupt was Some to make triggering an
interrupt a no-op. However the MSI-X interrupt routing already drops the
interrupt if the driver hasn't yet configured the vector so it is safe
to trigger the interrupt before device activation (e.g. balloon resize
request before driver loaded).

VirtioCommon still retains an Option<..> for the interrupt as the
interrupt is not known until activation time (after this has been
created). A helper VirtioCommon::trigger_interrupt() has been added to
handle this.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:15:37 +00:00
Rob Bradford
fe6bcd0376 virtio-devices: pci: Clear all config state on reset
Per the virtio spec a device reset must return the device to its
power-on state. The reset path was only zeroing queue_select. Add
VirtioPciCommonConfig::reset() and call it from the transport's reset
path so the configuration is cleared. Also relax the condition to allow
the device to be reset at any time to match the spec.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:15:37 +00:00
Rob Bradford
d573f1bf96 virtio-devices: Make reset() best-effort on backend failures
The virtio specification treats reset as the recovery operation and so
must take the device back to a fresh state, and the driver waits for the
status read-back to converge before continuing. There is no defined way
for the device to report a reset failure to the driver.

Previously the implementations of reset() would return early and not
complete all their cleanup leaving them in an inconsistent state. Now
log errors and continue through the execution.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-04-27 07:15:37 +00:00
Rob Bradford
733d1fe553 virtio-devices: vhost_user: Consolidate reset() into VhostUserCommon
The four vhost-user device wrappers (blk, fs, generic_vhost_user, net)
each carried an identical reset() body that resumed the worker thread,
asked the backend to reset, signalled kill_evt and dropped interrupt_cb.
Move the shared body into VhostUserCommon::reset() so behaviour stays in
one place.

No behavioural change.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Assisted-by: Claude:claude-opus-4-7
2026-04-27 07:15:37 +00:00
Rob Bradford
92229a60ed tests: Report stderr/stdout from restored child in test_ovs_dpdk
To aid debugging of this test failing print the output from the restored
VMM instance too.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:15:37 +00:00
Rob Bradford
466f50e72c tests: parallelise live-migration virtio-fs tests
`test_live_migration_virtio_fs` and its `_local` variant lived in
`common_sequential` because they shared `~/workloads/shared_dir` as
the virtiofsd backing and wrote/deleted the same `migration_test_file`
and `post_migration_file` paths inside it. Two instances running
concurrently would race on those files.

Give each invocation its own backing directory under `guest.tmp_dir`,
which is already per-test unique and gets cleaned up by the `TempDir`
drop. The test logic is otherwise unchanged. Move both wrappers and
the helper from `common_sequential` to `common_parallel` and update
the sequential-tests comment accordingly.

The boot footprint is small (512 MB src + 512 MB dest), so two
concurrent instances comfortably fit alongside the rest of the
parallel suite. The remaining sequential live-migration tests
(balloon, NUMA) genuinely need their isolation slot for memory
headroom.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:13:16 +00:00
Rob Bradford
5b2a5e639c tests: Reduce hugepages reservation
Reduce `nr_hugepages` from 12 GB to 6 GB on both architectures. The
number if huge pages needed (if all the tests run at once) is 4GiB so
this gives 50% headroom.

This should reduce the number of tests that fail/flake out due to lack
of memory.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:13:16 +00:00
Rob Bradford
2fb08c4b5d tests: Consolidate live migration tests into common scopes
Move the live migration tests themselves into the common scopes allowing
the tests to now run interleaved together hopefully reducing CI time.

On MSHV the live migration tests are now not compiled in rather than
compiled in and skipped (as the helpers are not compiled in for MSHV.)

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:13:16 +00:00
Rob Bradford
d9395b9773 tests: Move live migration helpers to common::utils
Move the helper methods used for live migration to the common utils
(like many other tests use).

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:13:16 +00:00
Rob Bradford
c118606d64 tests: Fold live migration tests into x86-64 script
Move the live migration test running from their own script into the
x86-64 script (on aarch64 they were already in the same script.) They
were historically separate as they were new. Now they are established it
makes sense for them to be combined.

The timeout in the GitHub workflow has been extended to accommodate the
extra work in the same step.

The Rust test scopes are unchanged - the running of the tests has been
moved.

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:13:16 +00:00
Rob Bradford
5909ce85ed tests: Standardise live-migration runner on MIGRATABLE_VERSION
The aarch64 integration script hardcoded `LAST_RELEASE_VERSION="v39.0"`
for the live-upgrade binary download, while the live-migration runner
already accepts a `MIGRATABLE_VERSION` env override with a `vxx.0`
regex check. Standardise the aarch64 script on the same env-override
block so both arches honour the same knob with the same validation.

Default is unchanged (v39.0).

Assisted-by: Claude:Opus-4.7
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:13:16 +00:00
Rob Bradford
6527fc22c0 vhost_user_block: Only support raw files
This daemon is only a testing tool used during integration testing. The
previous code auto-detected the image type from the file's magic bytes
and opened qcow2 images via QcowFile. Such behaviour has been the cause
of security issues in the past.

Drop the qcow2 detection and open path so only raw images are handled.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-27 07:12:49 +00:00
Muminul Islam
842d02fdd9 hypervisor: mshv: Validate GPA mapping with GvaGpaValid flag
The GvaGpaValid flag in the intercept message indicates whether
the provided GPA corresponds to the decoded GVA. Without checking
this flag, the emulator may incorrectly use a stale GPA mapping
when the hypervisor invalidates it.

Add a check for the GvaGpaValid flag before using the cached
(GVA, GPA) mapping. If the flag is clear, use a sentinel value
to force translate() to perform a proper hypercall-based
translation instead of using an invalid cached mapping.

Signed-off-by: Pedro Barbuda <pbarbuda@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-25 19:14:56 +00:00
Dylan Reid
326cd88074 virtio-devices: balloon: Clamp range to region before PUNCH_HOLE
release_memory_range took (range_base, range_len) verbatim from a
guest-controlled descriptor (free-page-reporting and inflate paths) and
called fallocate(PUNCH_HOLE | KEEP_SIZE) on the backing file before any
length check ran. find_region only validates that range_base lands
inside *some* region; range_len can extend past the region's end. When
the operator uses --memory-zone file=PATH against a host file larger
than the zone, the punch zeroes host file content past the guest's
memory extent.

Free-page reporting is advisory, so the VMM is allowed to act on a
subset of a free range. Clamp range_len to the bytes that actually fit
within the region returned by find_region. Going past the end of a
region is most likely a guest bug so log it.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-25 18:35:21 +00:00
Philipp Schuster
6d01695e5c vmm: avoid pause deadlock on CPU hotplug MMIO
# TL;DR

In https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7990 we
replaced the old deadlock with another deadlock. This commit finally
resolves (hopefully) all dead locks on that code path by not holding
`CpuManager::vcpu_states` while waiting for vCPU pause acknowledgements.
A vCPU can receive the pause kick while servicing the ACPI CPU hotplug
MMIO device, and that MMIO path also needs `vcpu_states`. Holding the
mutex across the wait phase deadlocks pause against that MMIO access.

# Problem

`signal_vcpus()` used to lock `CpuManager::vcpu_states` for the whole
function, signal every vCPU, and then wait for each vCPU to acknowledge
the kick.

That lock scope is too wide. A vCPU is allowed to observe the kick in
userspace rather than returning directly from `KVM_RUN`. During boot,
`vcpu0` can be in an MMIO access on the ACPI CPU hotplug device when
pause arrives. `AcpiCpuHotplugController::read()` and `write()` both
lock `vcpu_states` to inspect or update the selected vCPU state.

The deadlock looks like this:

    VMM thread                           vCPU thread
    ----------                           ----------
    lock(vcpu_states)
    signal_vcpus()
    wait for ack  ---------------------> receives pause kick
                                          enters ACPI CPU hotplug MMIO
                                          lock(vcpu_states)  [blocks]
    wait for ack  <--------------------- cannot set vcpu_run_interrupted

The VMM thread waits for `vcpu_run_interrupted` to flip, but the vCPU
cannot reach the pause acknowledgement path because it is sleeping on
the same mutex.

The debug logs matched that cycle exactly: signal delivery kept
working, `vcpu0` stayed in one unmatched `run()` invocation, the stuck
thread sampled in `futex_do_wait`, and the backtrace pointed at
`AcpiCpuHotplugController::read()`.

# Reproducer

This was reproducible by continuously issuing `pause()` / `resume()`
from while a Linux guest was still booting. That boot-tim window
reliably exercises the ACPI CPU hotplug MMIO access that participates in
the deadlock. Once the guest had finished booting, the problem became
much harder to trigger (as there is no MMIO operation without explicit
CPU plugging).

# Solution

Keep the existing two-phase behavior so all vCPUs are still signalled
before the wait phase, but narrow the lifetime of the `vcpu_states`
mutex. Reacquire it only long enough to access one `VcpuState` at a
time in each phase.

That preserves the original pause semantics and the fast signal-all /
wait-all structure, while removing the lock inversion with the ACPI CPU
hotplug MMIO path.

This also remains safe if a vCPU is hot-removed while pause is in
progress. Hot-remove does not shrink `vcpu_states`; it stops the thread
and clears the `VcpuState` handle in place. `signal_vcpus()` can
therefore snapshot the vector length up front, and if a vCPU disappears
between the signal and wait phases,
`wait_until_signal_acknowledged()` will observe `handle.is_none()` and
return successfully.

The interruption handshake itself lives in atomics inside each
`VcpuState`. The outer mutex is only needed to reach the state objects,
not to keep the acknowledgement protocol correct. Dropping the mutex
between iterations therefore does not weaken the pause protocol, but it
does allow MMIO handlers and other `vcpu_states` users to make forward
progress while the VMM waits for the kick to be observed.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-24 10:03:06 +00:00
dependabot[bot]
75e6e694a2 build(deps): bump the non-rust-vmm group across 2 directories with 22 updates
Bumps the non-rust-vmm group with 18 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [bitflags](https://github.com/bitflags/bitflags) | `2.11.0` | `2.11.1` |
| [clap](https://github.com/clap-rs/clap) | `4.6.0` | `4.6.1` |
| [libc](https://github.com/rust-lang/libc) | `0.2.184` | `0.2.185` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.0` | `1.23.1` |
| [io-uring](https://github.com/tokio-rs/io-uring) | `0.7.11` | `0.7.12` |
| [bitfield-struct](https://github.com/wrenger/bitfield-struct-rs) | `0.12.1` | `0.13.0` |
| [rand](https://github.com/rust-random/rand) | `0.10.0` | `0.10.1` |
| [async-signal](https://github.com/smol-rs/async-signal) | `0.2.13` | `0.2.14` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.59` | `1.2.60` |
| [fastrand](https://github.com/smol-rs/fastrand) | `2.3.0` | `2.4.1` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.91` | `0.3.95` |
| libredox | `0.1.15` | `0.1.16` |
| [openssl-src](https://github.com/alexcrichton/openssl-src-rs) | `300.5.5+3.5.5` | `300.6.0+3.6.2` |
| [openssl-sys](https://github.com/rust-openssl/rust-openssl) | `0.9.112` | `0.9.114` |
| [pkg-config](https://github.com/rust-lang/pkg-config-rs) | `0.3.32` | `0.3.33` |
| [portable-atomic-util](https://github.com/taiki-e/portable-atomic-util) | `0.2.6` | `0.2.7` |
| [rand_core](https://github.com/rust-random/rand_core) | `0.10.0` | `0.10.1` |
| [wasip2](https://github.com/bytecodealliance/wasi-rs) | `1.0.2+wasi-0.2.9` | `1.0.3+wasi-0.2.9` |

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

| Package | From | To |
| --- | --- | --- |
| [bitflags](https://github.com/bitflags/bitflags) | `2.11.0` | `2.11.1` |
| [clap](https://github.com/clap-rs/clap) | `4.6.0` | `4.6.1` |
| [libc](https://github.com/rust-lang/libc) | `0.2.184` | `0.2.185` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.0` | `1.23.1` |
| [rand](https://github.com/rust-random/rand) | `0.10.0` | `0.10.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.59` | `1.2.60` |
| [fastrand](https://github.com/smol-rs/fastrand) | `2.3.0` | `2.4.1` |
| [js-sys](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.91` | `0.3.95` |
| [pkg-config](https://github.com/rust-lang/pkg-config-rs) | `0.3.32` | `0.3.33` |
| [rand_core](https://github.com/rust-random/rand_core) | `0.10.0` | `0.10.1` |
| [wasip2](https://github.com/bytecodealliance/wasi-rs) | `1.0.2+wasi-0.2.9` | `1.0.3+wasi-0.2.9` |



Updates `bitflags` from 2.11.0 to 2.11.1
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.11.0...2.11.1)

Updates `clap` from 4.6.0 to 4.6.1
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.0...clap_complete-v4.6.1)

Updates `libc` from 0.2.184 to 0.2.185
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.185/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.184...0.2.185)

Updates `uuid` from 1.23.0 to 1.23.1
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.0...v1.23.1)

Updates `io-uring` from 0.7.11 to 0.7.12
- [Commits](https://github.com/tokio-rs/io-uring/compare/v0.7.11...v0.7.12)

Updates `bitfield-struct` from 0.12.1 to 0.13.0
- [Release notes](https://github.com/wrenger/bitfield-struct-rs/releases)
- [Commits](https://github.com/wrenger/bitfield-struct-rs/compare/0.12.1...0.13.0)

Updates `rand` from 0.10.0 to 0.10.1
- [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.0...0.10.1)

Updates `async-signal` from 0.2.13 to 0.2.14
- [Release notes](https://github.com/smol-rs/async-signal/releases)
- [Changelog](https://github.com/smol-rs/async-signal/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/async-signal/compare/v0.2.13...v0.2.14)

Updates `cc` from 1.2.59 to 1.2.60
- [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.59...cc-v1.2.60)

Updates `fastrand` from 2.3.0 to 2.4.1
- [Release notes](https://github.com/smol-rs/fastrand/releases)
- [Changelog](https://github.com/smol-rs/fastrand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/fastrand/compare/v2.3.0...v2.4.1)

Updates `js-sys` from 0.3.91 to 0.3.95
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `libredox` from 0.1.15 to 0.1.16

Updates `openssl-src` from 300.5.5+3.5.5 to 300.6.0+3.6.2
- [Release notes](https://github.com/alexcrichton/openssl-src-rs/releases)
- [Commits](https://github.com/alexcrichton/openssl-src-rs/commits)

Updates `openssl-sys` from 0.9.112 to 0.9.114
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.112...openssl-sys-v0.9.114)

Updates `pkg-config` from 0.3.32 to 0.3.33
- [Changelog](https://github.com/rust-lang/pkg-config-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/pkg-config-rs/compare/0.3.32...0.3.33)

Updates `portable-atomic-util` from 0.2.6 to 0.2.7
- [Release notes](https://github.com/taiki-e/portable-atomic-util/releases)
- [Changelog](https://github.com/taiki-e/portable-atomic-util/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/portable-atomic-util/compare/v0.2.6...v0.2.7)

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

Updates `wasip2` from 1.0.2+wasi-0.2.9 to 1.0.3+wasi-0.2.9
- [Commits](https://github.com/bytecodealliance/wasi-rs/compare/wasip2-1.0.2...wasip2-1.0.3)

Updates `wasm-bindgen` from 0.2.114 to 0.2.118
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.114...0.2.118)

Updates `wasm-bindgen-macro` from 0.2.114 to 0.2.118
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.114...0.2.118)

Updates `wasm-bindgen-macro-support` from 0.2.114 to 0.2.118
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.114...0.2.118)

Updates `wasm-bindgen-shared` from 0.2.114 to 0.2.118
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.114...0.2.118)

Updates `bitflags` from 2.11.0 to 2.11.1
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.11.0...2.11.1)

Updates `clap` from 4.6.0 to 4.6.1
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.0...clap_complete-v4.6.1)

Updates `libc` from 0.2.184 to 0.2.185
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.185/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.184...0.2.185)

Updates `uuid` from 1.23.0 to 1.23.1
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.0...v1.23.1)

Updates `rand` from 0.10.0 to 0.10.1
- [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.0...0.10.1)

Updates `cc` from 1.2.59 to 1.2.60
- [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.59...cc-v1.2.60)

Updates `fastrand` from 2.3.0 to 2.4.1
- [Release notes](https://github.com/smol-rs/fastrand/releases)
- [Changelog](https://github.com/smol-rs/fastrand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/fastrand/compare/v2.3.0...v2.4.1)

Updates `js-sys` from 0.3.91 to 0.3.95
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `pkg-config` from 0.3.32 to 0.3.33
- [Changelog](https://github.com/rust-lang/pkg-config-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/pkg-config-rs/compare/0.3.32...0.3.33)

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

Updates `wasip2` from 1.0.2+wasi-0.2.9 to 1.0.3+wasi-0.2.9
- [Commits](https://github.com/bytecodealliance/wasi-rs/compare/wasip2-1.0.2...wasip2-1.0.3)

Updates `wasm-bindgen` from 0.2.114 to 0.2.118
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.114...0.2.118)

Updates `wasm-bindgen-macro` from 0.2.114 to 0.2.118
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.114...0.2.118)

Updates `wasm-bindgen-macro-support` from 0.2.114 to 0.2.118
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.114...0.2.118)

Updates `wasm-bindgen-shared` from 0.2.114 to 0.2.118
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/compare/0.2.114...0.2.118)

---
updated-dependencies:
- dependency-name: bitflags
  dependency-version: 2.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.185
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.23.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: io-uring
  dependency-version: 0.7.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bitfield-struct
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: rand
  dependency-version: 0.10.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: async-signal
  dependency-version: 0.2.14
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.60
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: fastrand
  dependency-version: 2.4.1
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.95
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libredox
  dependency-version: 0.1.16
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: openssl-src
  dependency-version: 300.6.0+3.6.2
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: openssl-sys
  dependency-version: 0.9.114
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: pkg-config
  dependency-version: 0.3.33
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: portable-atomic-util
  dependency-version: 0.2.7
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand_core
  dependency-version: 0.10.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasip2
  dependency-version: 1.0.3+wasi-0.2.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bitflags
  dependency-version: 2.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.185
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.23.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand
  dependency-version: 0.10.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.60
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: fastrand
  dependency-version: 2.4.1
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: js-sys
  dependency-version: 0.3.95
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: pkg-config
  dependency-version: 0.3.33
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand_core
  dependency-version: 0.10.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasip2
  dependency-version: 1.0.3+wasi-0.2.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen
  dependency-version: 0.2.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro
  dependency-version: 0.2.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-macro-support
  dependency-version: 0.2.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasm-bindgen-shared
  dependency-version: 0.2.118
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-24 00:47:54 +00:00
Philipp Schuster
435d0ad47c tests: increase CI integration test parallelism
Increase the number of parallel integration tests in CI to save ~3-5
minutes per x86_64 run. The thread limit is driven by RAM and disk
space constraints, not CPU availability.

A new `PARALLEL_INTEGRATION_TESTS_NUM` environment variable controls
the thread count. In CI it is set explicitly (12 for x86_64, 25 for
ARM64); locally it falls back to `nproc / 4`, preserving the previous
behavior.

Only the first test group (`common_parallel`, `live_migration_parallel`)
uses the overridden value - subsequent groups (dbus_api, fw_cfg,
ivshmem, aarch64_acpi) continue to use the `nproc / 4` default.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-23 20:23:14 +00:00
Anatol Belski
63cc26e1ce block: vhd: Test that physical size includes footer
The physical size of a fixed VHD is the data region plus the 512
byte footer. Verify it differs from the logical size.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:17:54 +00:00
Anatol Belski
07ce8e0745 block: vhd: Test that resize is rejected
Fixed VHDs do not support resize. Verify the error is returned.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:17:54 +00:00
Anatol Belski
8a4cd9b15d block: vhd: Test that try_clone preserves backend dispatch
Verify that a cloned disk produces the same async I/O backend
as the original for both sync and io_uring paths.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:17:54 +00:00
Anatol Belski
95cbb2048a block: vhd: Test backend dispatch
Verify that create_async_io dispatches to the correct backend
depending on use_io_uring. The sync backend does not support
batch requests, while the io_uring backend does.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:17:54 +00:00
Anatol Belski
66d211e737 block: vhd: Add test for correct logical size
Verify that FixedVhdDisk::new with the sync backend reads the VHD
footer and reports the correct logical size.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:17:54 +00:00
Anatol Belski
d42ea61a5f block: Use FixedVhdDisk in factory, remove old wrappers
Update open_fixed_vhd to construct FixedVhdDisk instead of choosing
between FixedVhdDiskAsync and FixedVhdDiskSync. The io_uring decision
is now made inside FixedVhdDisk::create_async_io().

Remove FixedVhdDiskSync and FixedVhdDiskAsync DiskFile wrapper structs
from fixed_vhd_sync.rs and fixed_vhd_async.rs. Only the FixedVhdSync
and FixedVhdAsync AsyncIo worker structs remain in those files.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:17:54 +00:00
Anatol Belski
cb13100fe3 block: vhd: Add unified FixedVhdDisk
Introduce FixedVhdDisk as a single DiskFile wrapper around FixedVhd.
It accepts a use_io_uring flag at construction time and dispatches to
FixedVhdSync or FixedVhdAsync inside create_async_io() accordingly.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:17:54 +00:00
Anatol Belski
76069a73d6 block: raw: Add physical size test
Verify that physical size of a sparse file is less than
logical size.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
86e4ccecb6 block: raw: Add resize test
Verify that resize succeeds and updates the logical size.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
f209e571af block: raw: Add try_clone backend preservation tests
Verify that try_clone preserves the backend variant for
each RawBackend: sync, AIO and io_uring.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
fa247864fe block: raw: Add async I/O dispatch tests
Verify each RawBackend variant stores correctly and
create_async_io dispatches to the matching constructor.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
2f74d56812 block: raw: Add test for correct logical size
Verify that RawDisk with the sync backend reports the correct
logical size for a temporary file. Introduce make_raw_file helper
and TEST_SIZE constant for reuse in subsequent tests.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
701767e5be block: Use RawDisk in factory, remove old wrappers
Update open_raw to construct RawDisk instead of choosing between
RawFileDisk, RawFileDiskSync and RawFileDiskAio. The backend decision
is now made inside RawDisk::create_async_io.

Remove the DiskFile wrapper structs from raw_sync.rs, raw_async.rs
and raw_async_aio.rs. Only the AsyncIo worker structs remain in those
files. Reduce their module visibility to pub(crate).

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
556cb6be46 performance-metrics: Use RawDisk for AIO micro benchmark
Replace direct RawFileAsyncAio construction with RawDisk
and create_async_io, consistent with the unified API.

Assisted-by: Claude:Opus-4.6
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
4d0aa530f2 fuzz: Use RawDisk for block fuzz target
Update the fuzz target to use RawDisk instead of RawFileDiskSync,
consistent with the unified API.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
10d4447697 block: raw: Impl DiskFile and AsyncDiskFile for RawDisk
Add the DiskFile marker and AsyncDiskFile with try_clone and
create_async_io. The dispatch creates RawFileSync, RawFileAsync,
or RawFileAsyncAio depending on the backend selected at
construction. Alignment handling is left to the workers as is,
to be centralized separately per #8050.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
0955a40060 block: raw: Impl Resizable for RawDisk
Use the block device aware resize from RawFileDisk. For block
devices, verify the externally set size matches instead of
calling ftruncate. For regular files, truncate as usual.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Anatol Belski
f84a940c43 block: raw: Add RawDisk struct with RawBackend enum
Introduce the unified DiskFile wrapper for raw disk images. The
RawBackend enum selects between sync, io_uring, and AIO backends
at construction time.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-23 20:15:49 +00:00
Demi Marie Obenour
0a7c613da0 vm-allocator: Remove a redundant check for zero
alignment.is_power_of_two() already implies alignment != 0.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-04-23 12:13:19 +00:00
Anatol Belski
814e8edc0c block: Remove async_io::DiskFile trait
The trait has no implementations. All format backends now implement
the composable disk_file traits instead. The DiskFileError and
AsyncIo types remain in async_io.rs as they are still used by
format workers.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-22 18:04:16 +00:00
Anatol Belski
835caf9413 block: Remove DiskBackend dispatch enum
All disk format backends now implement AsyncFullDiskFile directly.
The DiskBackend enum that dispatched between Legacy and Next arms
is no longer needed since the factory returns trait objects and vmm
no longer constructs format types manually.

Replace DiskBackend with Box<dyn AsyncFullDiskFile> in the Block
struct and its constructor. Remove the DiskBackend::Next wrapping
in device_manager and the fuzz target.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-22 18:04:16 +00:00
Anatol Belski
6b6150ebfc vmm: device_manager: Use block factory for disk opening
Replace the manual match block that constructed each disk format
backend with a single call to block::factory::open_disk. The factory
handles file opening, format detection, async/sync backend selection
and logging internally.

Remove imports and errors for individual format types and helper
functions that are no longer called directly. The factory returns
BlockError with path and operation context attached, surfaced via
the existing Disk variant.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-22 18:04:16 +00:00
Anatol Belski
03a5c29c48 block: factory: Add test for sync fallback
Verify that open_disk() falls back to synchronous backend when both
io_uring and AIO are disabled, and that the returned disk reports
the correct logical size.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-22 18:04:16 +00:00
Anatol Belski
37693aa141 block: factory: Add test for readonly open
Verify that open_disk() succeeds with readonly=true on a RAW image.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-22 18:04:16 +00:00
Anatol Belski
78784c2a3f block: factory: Add test for QCOW2 image detection
Create a minimal QCOW2 temp file via QcowFile::new() and verify
that open_disk() detects it as ImageType::Qcow2.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-22 18:04:16 +00:00
Anatol Belski
01c4e0512f block: factory: Add test for RAW image detection
Verify that open_disk() detects a plain temporary file as RAW and
returns a working backend with synchronous fallback.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-22 18:04:16 +00:00
Anatol Belski
9ada7a9afd block: factory: Add test for nonexistent path
Verify that open_disk() returns BlockErrorKind::Io when the disk
image file does not exist.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-22 18:04:16 +00:00
Anatol Belski
cba2b7f773 block: factory: Add disk image factory module
Introduce block::factory with open_disk() as the single entry point
for opening disk images. It handles file opening, format detection,
async I/O probing, and backend construction.

Per format helpers (open_fixed_vhd, open_raw, open_qcow2, open_vhdx)
prefer io_uring over AIO over synchronous fallback. Warnings only
fire when a backend was eligible but its runtime probe failed, not
when the user intentionally disabled it.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-22 18:04:16 +00:00
Wei Liu
d56253196b vmm: Enforce threads_per_core to be at most two
Microsoft Hypervisor's hyperclear technology supports at most two
threads per core.

Practically all x64 CPUs nowadays only support two threads per core.
Enforce this in the common code.

Assisted-by: OpenAI:ChatGPT-5.4
[Test cases written by an LLM ]
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-04-22 01:01:06 +00:00
Demi Marie Obenour
7c44f49293 block: Move request code to its own module
There is no reason for most of the Request struct to be writable from
anywhere in the codebase.  Encapsulate it.

Use getter functions for access outside the request module.  Replace the
trivial setter for the writeback field with direct assignment.

No functional change intended.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-04-21 23:35:26 +00:00
Wei Liu
8f972567d0 docs: Standardize the format for disclosing LLM-assisted changes
This is adopted from the Linux kernel development process.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-04-21 21:48:59 +00:00
Anatol Belski
21cd13df01 block: Rename AsyncDiskFile::new_async_io to create_async_io
The new_ prefix in Rust conventionally denotes constructors that return
Self (e.g. Vec::new(), File::new()). AsyncDiskFile::new_async_io does
not return Self. It is a factory method that constructs and returns a
Box<dyn AsyncIo> worker bound to the disk file descriptor and
metadata. The create_ prefix communicates this: the caller receives
a freshly constructed object of a different type.

This rename touches every format backend in block plus two external
callers in virtio-devices and performance-metrics. Every change is a
mechanical s/new_async_io/create_async_io/ substitution. No functional
change.

Ref: #7877 (task 3.2.8)
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-21 10:36:52 +00:00
Saravanan D
5a14d0e2e0 vmm: clear VFIO MMIO regions in DeviceManager::drop
DeviceManager and VfioPciDevice both hold Arc<MmapRegion> for
each VFIO BAR mmap window. During VM shutdown, VfioPciDevice
drops after DeviceManager::Drop::drop (via device_tree
field drop). Without clearing DeviceManager's clones first,
VfioPciDevice::unmap_mmio_regions decrements the Arc but
does not reach zero, munmap never fires, the VFIO device
file VMAs survive, and VFIO_GROUP_UNSET_CONTAINER returns
EBUSY.

Clear DeviceManager's mmio_regions in Drop::drop so
VfioPciDevice is the sole Arc owner at drop time and ensure
VFIO_GROUP_UNSET_CONTAINER ioctl success.

Remove redundant .clone() on the mmio_regions() return value
in the eject_device() hot-unplug path.

Add detail comments

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-04-20 18:10:17 +00:00
Anatol Belski
7294ca99cf block: raw: Return BlockResult from RawFileAsync::new
Change RawFileAsync::new() from std::io::Result to BlockResult,
aligning it with RawFileAsyncAio::new(). Each fallible call inside
the constructor now maps to BlockErrorKind::Io explicitly.

FixedVhdAsync::new() follows the same change since its only
fallible operation is constructing a RawFileAsync. The intermediate
DiskFileError::NewAsyncIo wrapping in both new_async_io() call
sites is no longer needed and is removed.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:27 +00:00
Anatol Belski
cd9fc52472 block: vhdx: Classify VhdxError variants in VhdxDiskSync::new
Replace the blanket BlockErrorKind::Io mapping with an explicit
match on all VhdxError variants:

  NotVhdx, ParseVhdxHeader, ParseVhdxMetadata,
  ParseVhdxRegionEntry  => InvalidFormat
  ReadBatEntry           => CorruptImage
  ReadFailed, WriteFailed => Io

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:27 +00:00
Anatol Belski
4416a582c8 block: vhdx: Remove catch all in physical_size boundary
Vhdx::physical_size() can only return Error::GetFileMetadata.
Replace the catch-all arm with unreachable!() so future error
variants are not silently mapped to a generic Io classification.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:27 +00:00
Anatol Belski
da64acb775 block: vhd: Propagate logical_size error at boundary
Replace .unwrap() on FixedVhd::logical_size() with map_err in
DiskSize::logical_size() and new_async_io() for both FixedVhdDiskSync
and FixedVhdDiskAsync. The call is infallible today but unwrap hides
that assumption from callers and would panic if it ever changed.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:27 +00:00
Anatol Belski
a8d339c9e7 block: vhd: Remove catch all in physical_size boundary
FixedVhd::physical_size() can only return Error::GetFileMetadata.
Replace the catch-all arm with unreachable!() so future error
variants are not silently mapped to a generic Io classification.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:27 +00:00
Anatol Belski
519acda794 block: qcow: Test compressed cluster read via QcowFile
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>
2026-04-20 18:09:12 +00:00
Anatol Belski
365ed236cc block: qcow: Test compressed cluster read via QcowDiskAsync
Write a known data pattern, compress all clusters in place, reopen
through QcowDiskAsync, and read back from four concurrent queues
on separate threads. Each queue independently decompresses and
returns the correct data, validating the Arc<dyn Decoder> sharing.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:12 +00:00
Anatol Belski
16abb28b85 block: qcow: Test compressed cluster read via QcowDiskSync
Write a known data pattern to a QCOW2 image, convert all allocated
clusters to compressed format using compress_allocated_clusters,
reopen the image through QcowDiskSync, and verify that reading back
the full cluster returns the original data.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:12 +00:00
Anatol Belski
79c6ce43ef block: qcow: Test decompress_cluster with corrupt input
Verify that decompress_cluster returns EIO when given invalid
compressed data that the decoder cannot process.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:12 +00:00
Anatol Belski
ed9154b39d block: qcow: Test decompress_cluster deflate roundtrip
Compress a known 64K buffer with raw deflate, pass it through
decompress_cluster with ZlibDecoder, and verify the output matches
the original data.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:12 +00:00
Anatol Belski
a502354619 block: qcow: Test pread_alloc with offset reads and EOF
Verify that pread_alloc returns the correct data for a full read
from the start and a partial read at an arbitrary offset. Also
confirm that reading past the end of file produces an error.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:12 +00:00
Anatol Belski
158882e6dd block: qcow: Add compress_allocated_clusters test helper
Add a test utility that converts standard uncompressed clusters in
a QCOW2 image into compressed clusters in place. It walks the L1/L2
tables, compresses each allocated cluster with raw deflate, appends
the compressed payload at the end of the file, and rewrites the L2
entry with the compressed layout.

This enables end to end testing of the compressed read path without
external tools like qemu-img.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:12 +00:00
Anatol Belski
5504ad753a block: qcow: Move compressed read decompression out of lock
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>
2026-04-20 18:09:12 +00:00
Anatol Belski
659f7c17e5 block: qcow: Cache cluster_size in per queue structs
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>
2026-04-20 18:09:12 +00:00
Anatol Belski
d9b188c1be block: qcow: Add pread_alloc and decompress_cluster helpers
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>
2026-04-20 18:09:12 +00:00
Anatol Belski
78a05ab0c1 test_infra: Test stale process group is replaced
Spawn a process that exits immediately, then spawn another under
the same test name. Verify the stale group is detected and a fresh
group is created for the second child.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:02 +00:00
Anatol Belski
0a2d078479 test_infra: Test cleanup of unknown name returns false
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:02 +00:00
Anatol Belski
6e8c2bd2b3 test_infra: Test multiple spawns share one process group
Spawn two processes under the same test name and verify they
have the same group PID. Cleanup kills both.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:02 +00:00
Anatol Belski
6453a9a91a test_infra: Test ProcessRegistry spawn and cleanup
Spawn a sleep process through the registry, verify it is alive,
call cleanup and verify the process was killed.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:02 +00:00
Anatol Belski
a597c74385 performance-metrics: Replace pkill cleanup with ProcessRegistry
Use thread::Builder to give the test thread a name matching the
test so Guest picks it up automatically.  After every test, call
ProcessRegistry::cleanup() to kill the process group instead of
the old pkill based cleanup_stale_processes().

Remove cleanup_stale_processes() and its call sites.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:02 +00:00
Anatol Belski
18513ead0c test_infra: Wire spawn sites through ProcessRegistry
Route GuestCommand::spawn(), iperf3, and ethr spawns through
ProcessRegistry::spawn() when the guest has a test_name.  This
places every child process into the test's shared process group
so they can all be killed with a single killpg call.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:02 +00:00
Anatol Belski
3ab2c60489 test_infra: Add ProcessRegistry for per test process groups
Add a global registry that maps test names to process group IDs.
The first child spawned for a test creates a new process group
via setpgid(0, 0); subsequent children join it via setpgid(0, pgid).
A single killpg(pgid, SIGKILL) tears down all processes the test
spawned.

Also add Guest.test_name, populated automatically from the current
thread name at construction time.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-20 18:09:02 +00:00
Rob Bradford
9a14fdb7f8 virtio-devices: block: Correctly report number of bytes written
The driver needs to be notified with the number of bytes written by the
device. Ensure that the correct number of bytes is reported.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-20 18:08:34 +00:00
Rob Bradford
334b900fcd vhost_user_block: Correctly report number of used bytes
The number of bytes written into descriptors should be reported for
`add_used()`. Here it is either just the status byte or also the size of
serial ID for the block device.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-20 18:08:34 +00:00
Rob Bradford
7b07c3a194 virtio-devices: rng: Fill the entire descriptor chain
The virtio spec allows a chain of writable descriptors however the rng
device was assuming just a single writable descriptor. Instead fill in
all writable descriptors. There is no status byte (unlike e.g. block)
and instead 0 bytes used is used to indicate error.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-20 18:08:34 +00:00
Rob Bradford
ae3282dc86 virtio-devices: pmem: Write a status respone for invalid commands
The virtio spec requires that a status response is always written on
error. This was missing from the path where we had a valid request but
not for one we support.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-20 18:08:34 +00:00
Rob Bradford
8685eb5c53 virtio-devices: pmem: Relax descriptor size check
The virtio spec allows the use of larger descriptors (for future
expansion). Relax the bounds check to only reject descriptors that are
too small.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-20 18:08:34 +00:00
Rob Bradford
82d426162d virtio-devices: mem: Relax descriptor size check
The virtio spec allows the use of larger descriptors (for future
expansion). Relax the bounds check to only reject descriptors that are
too small.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-20 18:08:34 +00:00
Rob Bradford
2a089db269 virtio-devices: net: Report correct used length on TX and ctrl
The virtio spec says the used-ring length is bytes the device wrote to
device writable descriptors. The net TX descriptors are device readable
only (the device wrote nothing back) so the length needs to be 0. On the
ctrl queue the number of bytes reported was wrongly the size of the
status descriptor not the number of bytes written (the descriptor is
permitted to be larger).

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-20 18:08:34 +00:00
Muminul Islam
305451cce4 scripts: dev_cli: Add signal handling for graceful cleanup
During development, it is common to cancel a running test and
rerun it after making changes. However, pressing Ctrl+C while
dev_cli.sh runs long-running container commands (wget, qemu-img,
cargo build, etc.) does not reliably terminate the process.
Bash defers signal handling while a foreground process is
running, so the trap only fires after the docker run command
returns. This makes it difficult to cancel and restart quickly.

Fix this by introducing a run_container() wrapper that runs
docker in the background and uses 'wait', which is immediately
interruptible by signals. A cleanup() trap handler is set for
SIGINT and SIGTERM that kills the named container, the tracked
background PID, and any remaining child processes.

Each docker run invocation is assigned a unique --name based
on the script PID (clh-dev-$$) to allow targeted cleanup.
The interactive shell (cmd_shell) is left unwrapped since it
needs foreground terminal I/O.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-20 13:49:58 +00:00
Rob Bradford
ae646f9220 tests: Use wait_until() in test_pci_device_id
The SSH connection may fail initially when under load so use
`wait_until()` to allow retries.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-20 12:35:12 +00:00
Rob Bradford
a77b6231b4 tests: Ensure that virtiofsd has exited before hotplugging
Fix test flakiness where the virtiofsd daemon was still running and
hotplugging was trying to reach the old version. Cleanup the socket so
that waiting for it actually waits for the new instance.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-20 12:35:12 +00:00
Keith Adler
926dd1e141 vmm, devices: Add fw_cfg string item support
QEMU supports passing inline string values to the guest via fw_cfg
(-fw_cfg name=...,string=...). Cloud Hypervisor previously only
supported file-backed fw_cfg items. This adds the 'string' option
so users can pass values like OVMF's X-PciMmio64Mb without creating
a temporary file on the host.

Each fw_cfg item now accepts exactly one of 'file' or 'string'.
The FwCfgInvalidItem invariant is validated in PayloadConfig::validate()
(via FwCfgConfig::validate()), covering both CLI and JSON API paths.
The populate_fw_cfg match arm uses unreachable!() since validation
guarantees the invariant holds at that point.

CLI syntax:
  --fw-cfg-config items=[name=opt/ovmf/X-PciMmio64Mb,string=262144]

Signed-off-by: Keith Adler <kadler@cloudflare.com>
2026-04-20 09:39:56 +00:00
Keith Adler
e4e3375a8d vmm: move fw_cfg validation into PayloadConfig::validate()
Move FwCfgMissingKernel/Cmdline/Initramfs error variants from
ValidationError into PayloadConfigError. Change FwCfgConfig::validate()
to take &PayloadConfig instead of &VmConfig and return
PayloadConfigError. Wire the call through PayloadConfig::validate()
so both CLI and JSON API paths are covered.

Signed-off-by: Keith Adler <kadler@cloudflare.com>
2026-04-20 09:39:56 +00:00
Anirudh Rayabharam
a10d9a3099 tests: Ignore live migration tests on mshv arm64
Live migration is not yet supported on mshv arm64. Annotate the
applicable integration tests with cfg_attr to ignore them for that
configuration.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-04-17 18:05:01 +00:00
Ruben Hakobyan
ea2df946f6 ci: Add CI jobs for KVM SEV-SNP
Add build and clippy jobs for kvm+sev_snp+igvm+fw_cfg feature combination.

Signed-off-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Dylan Reid
d5179a73c2 vmm: use 64-bit BARs for hotplugged virtio block devices
Boot-time block devices on PCI segment 0 use 32-bit BARs so early
firmware can access them without additional identity mapping in the
firmware page tables. However, hot-plugged block devices are only ever
seen by the OS kernel which handles 64-bit BARs natively.

Switch hot-plugged block devices to 64-bit BARs to avoid exhausting the
scarce 32-bit MMIO window (typically 2-3 GB between RAM and 4 GB) when
many devices are hot-plugged.

Extract the BAR sizing decision into use_64bit_bar_for_virtio_device()
and thread an is_hotplug flag through add_virtio_pci_device(). Add unit
tests covering all relevant combinations.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-17 12:28:55 +00:00
Dylan Reid
d48d1dcd3d vmm: export full setup-header area for x86_64 kernels
The Linux x86 boot protocol defines the setup area as
(setup_sects + 1) * 512 bytes. Previously we exported only the
boot_params buffer (4096 bytes), which is wrong for kernels with
setup_sects >= 8 where the actual setup area exceeds boot_params.

Truncate or extend the existing buffer to the correct setup_sects-
derived length, reading any extra bytes directly from the kernel file.
This avoids an extra allocation in the common case (setup_sects <= 7)
and matches QEMU's fw_cfg_add_kernel() behavior in
hw/i386/x86-common.c.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
883ca3feb2 vmm: reserve memory regions for stage0 and VMSA on KVM SEV-SNP
A bootloader/firmware (e.g. stage0) and the VMSA page require dedicated
memory regions at fixed GPAs.

Add reserve_region_for_stage0() to allocate these regions before IGVM
loading begins:
- Stage0 at GPA 0xffc0_0000 (4 MB)
- VMSA page at GPA 0xffff_ffff_f000 (4 KB)

These reservations are KVM-only; MSHV handles stage0/VMSA placement
through its own isolated import path.

Also add fw_cfg device creation and SYS_statx to the vCPU seccomp
allowlist (needed by stage0's file access pattern).

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
75ed2c9f90 vmm: add KVM SEV-SNP support to IGVM loader
Adapt the IGVM loader to work with both MSHV and KVM backends, which
differ in page type constants, CPUID page layout, and VMSA handling.

Abstract page types into a PageTypeConfig struct populated at runtime
from the detected hypervisor, replacing hardcoded mshv_bindings constants.

Apply the VMSA register state to each vCPU via setup_sev_snp_regs(),
translating SevSelector attributes to KVM segment format using a bitfield
decoder.

KVM's SNP launch path sanitizes certain CPUID bits that could lead to
an insecure guest. If the VMM sets these bits, KVM rejects the CPUID
page import on the first attempt, requiring a retry with the
firmware-corrected values.

Pre-clear the known problematic bits before import to avoid the
reject-and-retry cycle:

- Leaf 0x1, ECX bit 24: TSC_DEADLINE (filtered by KVM)
- Leaf 0x7, EBX bit 1: SGX (filtered by KVM)
- Leaf 0x7, EDX: clear entirely (contains speculative features)
- Leaf 0x80000008, EBX bit 25: filtered by KVM
- Leaf 0x80000021, ECX: clear entirely

This keeps the CPUID page stable across launch updates and avoids
noisy error logs from the retry path.

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Co-authored-by: Dylan Reid <dgreid@fb.com>
Signed-off-by: Dylan Reid <dgreid@fb.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
b5ddcdc74a hypervisor: handle VcpuExit::MemoryFault for AP boot page conversions
During SNP boot all guest RAM is initially marked
KVM_MEMORY_ATTRIBUTE_PRIVATE. Pages imported via SNP_LAUNCH_UPDATE are
properly accepted by the guest, but generic RAM pages (e.g. the AP
trampoline at GPA 0xD000) are not. When stage0 on the BSP starts
secondary vCPUs via x2APIC, the APs try to execute from the trampoline
page through the shared mapping while KVM still has it marked private,
causing a KVM_EXIT_MEMORY_FAULT (flags=KVM_MEMORY_EXIT_FLAG_PRIVATE)
that previously fell through to the catch-all error, killing the VM.

Handle VcpuExit::MemoryFault by toggling the page's memory attribute
between private and shared based on the exit flags, allowing the vCPU
to retry the access.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
4a0cfa02de hypervisor: Handle KVM_HC_MAP_GPA_RANGE hypercalls
SEV-SNP guests will issue this hypercall to signal a change in the page
encryption status to the hypervisor.

Handle VcpuExit::Hypercall in the KVM vCPU run loop: decode the GPA,
page count, and private/shared attribute from the hypercall arguments,
then call KVM_SET_MEMORY_ATTRIBUTES to update the page state.

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
4b2538f522 hypervisor, vmm: Add support for KVM_SEV_SNP_LAUNCH_FINISH
Add the KVM_SEV_SNP_LAUNCH_FINISH ioctl, which finalizes the SNP
launch sequence and transitions the VM into a runnable encrypted
state.

Additionally, add KVM_SEV_SNP_LAUNCH_FINISH to the seccomp allowlist.

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
24db5e1efd hypervisor, vmm: Add support for KVM_SEV_SNP_LAUNCH_UPDATE
Implement the KVM_SEV_SNP_LAUNCH_UPDATE ioctl.

Extend Vm::import_isolated_pages() with a uaddrs parameter carrying
host virtual addresses, which KVM needs, unlike MSHV. Compute uaddrs
from guest memory mappings in the IGVM loader.

Add KVM_SEV_SNP_LAUNCH_UPDATE to the seccomp allowlist.

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
2e004521e0 hypervisor, vmm: Add KVM SEV_{INIT2, SNP_LAUNCH_START} support
Introduce the SevFd abstraction that wraps /dev/sev and implements the
KVM_SEV_INIT2 and KVM_SEV_SNP_LAUNCH_START ioctls for SEV-SNP VM
initialization on KVM.

Key changes:
- Add sev.rs with KvmSevInit and KvmSevSnpLaunchStart ioctl structs
  matching the kernel layout (linux/arch/x86/include/uapi/asm/kvm.h)
- Implement KVM_SEV_INIT2 and KVM_SEV_SNP_LAUNCH_START ioctls
- Set KVM_MEMORY_ATTRIBUTE_PRIVATE on newly created memory regions
  when guest_memfd is supported
- Widen SevSnpPageAccessProxy cfg gates from mshv-only to all
  sev_snp-enabled builds
- Make sev_snp_init a required trait method (remove default impl)
- Include KVM_SEV_SNP_LAUNCH_START in the seccomp allowlist
- Parse VMSA SEV features from IGVM and include them in the
  KVM_SEV_INIT2 ioctl

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Co-authored-by: Rob Bradford <rbradford@meta.com>
Signed-off-by: Rob Bradford <rbradford@meta.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
425609a8b5 vmm: parse IGVM file early and thread it through VM setup
Move IGVM file parsing from load_igvm() into a dedicated parse_igvm()
helper in igvm/mod.rs, and parse the file upfront in Vm::new() so the
resulting IgvmFile struct is available throughout VM initialization.

This is a prerequisite for extracting VMSA SEV features from the parsed
IGVM before issuing KVM_SEV_INIT2, which needs sev_features.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
4f1119a788 vmm: remove sev_snp_enabled parameter from payload loading
The load_payload and load_payload_async functions previously received a
sev_snp_enabled flag to decide whether to call load_igvm with or
without the host_data parameter. Replace this with a single code path
that always passes host_data behind a cfg(feature = "sev_snp") gate,
removing the runtime branch and the extra parameter threaded through
three call sites.

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
7d65187350 vmm: make RSDP address optional in configure_system
Change configure_system to take an Option<GuestAddress>
since rsdp is wrapped into an option anyways (we use configure
system to setup the mptables).

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
b545b2fc4e hypervisor, vmm: pass SNP guest policy to sev_snp_init
The SNP guest policy (AMD SEV-SNP ABI bits controlling SMT, migration,
debug, etc.) was previously hardcoded inside the MSHV implementation.
Widen Vm::sev_snp_init() to accept an SnpPolicy parameter so each
hypervisor backend receives the policy at init time.

Add get_default_sev_snp_guest_policy() in the VMM to construct the
default policy.

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
8ee0a07ab1 arch, hypervisor, vmm: skip vcpu setup when using igvm and kvm
When we use igvm + kvm, we setup the regs and sregs using the cpuid
page. We still need to setup the fpu in configure_vcpu.

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Ruben Hakobyan
c31f5d4998 vmm: allow IGVM payload alongside a kernel
Previously, the payload validation rejected an IGVM file combined with
a kernel or firmware.

Relax this constraint to allow an IGVM carrying a firmware (e.g Oak
stage0) to be paired with a separate kernel image.

This enables fw_cfg-style boot where stage0 loads a kernel provided
through fw_cfg rather than embedded in the IGVM file itself.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Keith Adler
cdbe43f423 hypervisor: kvm: Add GUEST_MEMFD and KVM_SET_USER_MEMORY_REGION2 support
Add support for guest_memfd (available in Linux kernel v6.8+), which
enables private memory for confidential VMs.

Key changes:
- Introduce UserMemoryRegion abstraction with guest_memfd fields
- Add From impls between kvm_userspace_memory_region2 and UserMemoryRegion
- Convert all KVM memory region operations from kvm_userspace_memory_region
  to kvm_userspace_memory_region2, with automatic fallback to v1 when
  guest_memfd is not supported
- Add set_user_memory_region() wrapper that dispatches to v1/v2 based on
  kvm_guest_memfd_supported capability
- Create guest_memfd via KVM_CREATE_GUEST_MEMFD ioctl when supported
- Extend KvmDirtyLogSlot to preserve region2 fields across dirty log
  start/stop cycles

This is prerequisite infrastructure for KVM-based confidential computing
that requires private guest memory backed by guest_memfd.

Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
2026-04-17 12:28:55 +00:00
Rob Bradford
12dd72d88f virtio-devices: balloon: Enable use with confidential VMs
Following the pattern used by the existing virtio devices make the
balloon device work with confidential VMs (e.g. SEV-SNP). This requires
advertising the VIRTIO_F_ACCESS_PLATFORM feature. Do not expose this to
the user as a controllable option and instead only enable in on the
"force" case.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 10:54:51 +00:00
Rob Bradford
5bd6fdc17d vmm: Rename force_iommu to force_access_platform
This a clearer name for it's purpose and now matches more closely what
is used for the virtio devices themselves.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 10:54:51 +00:00
Rob Bradford
0eca4d7c68 virtio-devices: Rename control parameter for VIRTIO_F_ACCESS_PLATFORM
Rename from iommu to access_platform_enabled. The original name was
iommu as this feature was exposed for devices behind an IOMMU however
this feature is also now used for confidential VMs so adopt a more
general name.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 10:54:51 +00:00
Rob Bradford
adc5bb7958 virtio-devices: Add a VirtioDevice reference to VirtioPciCommonConfig
Replace the stored AccessPlatform reference with one to the
VirtioDevice. By doing this not only does it allow the code to be
simplified but also now makes it virtio spec compliant by only
translating via the access platform if the feature is acknowledged.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 10:54:51 +00:00
Rob Bradford
128ee6d105 virtio-devices: Implement VirtioDevice::access_platform()
This forwards through to the VirtioCommon implementation and can be used
to simplify the virtio PCI access code.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 10:54:51 +00:00
Rob Bradford
636da215e3 virtio-devices: Add VirtioDevice::access_platform()
Adding this method to the trait will allow the virtio PCI code to access
a feature conditional version of the access platform and simplify the
logic.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 10:54:51 +00:00
Rob Bradford
81ee260ac3 virtio-devices: Use new VirtioCommon::access_platform() accessor
Use the new virtio feature gated accessor when creating the handlers for
the virtio devices. This now means that the translations via the
accessor will only be applied if the feature is acked in accordance with
the spec.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 10:54:51 +00:00
Rob Bradford
ea64d109c7 virtio-devices: Add feature acked gated accessor for AccessPlatform
Add VirtioCommon::access_platform() method. The virtio spec requires
that only if the feature is acked should the accesses be transformed via
the access platform implementation. This will enable that filtering.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 10:54:51 +00:00
Anatol Belski
e38f00644d tests: Replace manual losetup with create_loop_device() call
Use the ioctl based create_loop_device() helper instead of
shelling out to losetup in the file backed 4K alignment test.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-17 08:16:47 +00:00
Anatol Belski
4772235952 block: qcow: Fix O_DIRECT EINVAL in async io_uring path
Override AsyncIo::alignment() to report the actual device sector
size so that execute_async() correctly bounces misaligned guest
memory pointers.

Guard the io_uring fast path in resolve_read() with an alignment
check. When O_DIRECT is active, guest requests can have I/O sizes
smaller than the device sector size (e.g. 512 byte UEFI reads on
a 4096 byte sector device). The kernel rejects these with EINVAL.
Route such reads through scatter_read_sync() which uses AlignedBuf
and aligned_pread to satisfy O_DIRECT size and offset requirements.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-17 08:16:47 +00:00
Anatol Belski
98c533a501 block: qcow: Test O_DIRECT write and read roundtrip
Write 128K of patterned data and read it back with O_DIRECT
active to verify the aligned I/O paths produce correct results.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-17 08:16:47 +00:00
Anatol Belski
e67195ce48 block: qcow: Test sub sector O_DIRECT read
Verify that a 512 byte read from an allocated cluster succeeds
with O_DIRECT. This exercises the synchronous fallback path in
resolve_read() that is taken when alignment is nonzero.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-17 08:16:47 +00:00
Anatol Belski
dd79b1899d block: qcow: Test async alignment() with O_DIRECT
Verify that QcowAsync reports at least SECTOR_SIZE alignment
when O_DIRECT is active. Skipped on filesystems that do not
support O_DIRECT (e.g. tmpfs).

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-17 08:16:47 +00:00
Anatol Belski
854b686293 block: qcow: Test async alignment() returns SECTOR_SIZE
Verify that QcowAsync reports the default SECTOR_SIZE alignment
when O_DIRECT is not active.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-17 08:16:47 +00:00
Anatol Belski
11a86fee3e tests: Add QCOW2 direct I/O UEFI boot integration test
Boot a UEFI guest from a QCOW2 image with direct=on to exercise
the aligned I/O write path during early firmware operations.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-17 08:16:47 +00:00
Vincent Thomas
fd8ded9d78 block: Fix resize for block device backends
Block devices (LVM volumes, loop devices, RBD, etc.) cannot be resized
via ftruncate - they are resized externally. When vm.resize-disk is
called for a block device backend, verify the device size matches the
requested size instead of attempting ftruncate.

This enables the resize-disk API to work with block device backends by
validating the externally-resized device matches the expected size.

Signed-off-by: Vincent Thomas <vincent@v-thomas.com>
2026-04-17 07:57:35 +00:00
Vincent Thomas
e5dbf5242e virtio-devices: Make pause idempotent to prevent deadlock
Previously, calling pause() when already paused would wait on a barrier
for worker threads that were already parked, causing a deadlock.

This situation occurs when the VMM thread holds a device mutex while
calling an operation that triggers pause(), and a vCPU thread
simultaneously needs that same mutex for MMIO access. With slow I/O
backends (like RBD/Ceph), the timing window for this race is larger,
making the deadlock more likely to occur, see [0].

Make pause() idempotent by checking the paused state atomically and
returning early if already paused, avoiding the barrier wait.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7948#discussion_r305052509

Signed-off-by: Vincent Thomas <vincent@v-thomas.com>
2026-04-17 07:57:35 +00:00
Rob Bradford
67cf328a9e tests: Speed up test_pci_device_id()
This test was taking > 300s due to SSH backoffs.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Rob Bradford
d449983495 vmm: Be consistent with PCI bus reservation nomenclature
Our bus slots are now Reserved/Allocated/Free so change the method to
free it to free_device_id() and update error.

Also update to take u8 to match the other methods.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Pascal Scholz
18873d88d7 tests: Add an integration test for PCI device ID allocation errors
Adds a test that checks the correct error is returned on allocation of
an invalid device ID (one that is not in the range 0-31) and when trying
to allocate a reserved ID (such as that of the root bridge).

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Pascal Scholz
f81faad0a1 tests: Add an integration test to check duplicate PCI device IDs
This integration test verifies that the same device ID cannot be
allocated twice. Moreover, we check that the returned error matches our
expectations.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Pascal Scholz
f82eebc0b0 tests: Add an integration test to verify PCI device allocations
This commit adds an integration test to verify that the guest sees the
correct BDF. Moreover, we check that we can allocate a random free BDF
and that freeing BDFs works.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Pascal Scholz
8259f92909 tests: Return stderr when executing commands
If we want to test for error cases, it can be useful to inspect the
`stderr` of a `Command` to analyze the errors. For example, this allows
us to ensure that a `Command` returns an `IoError` by parsing the
error trace, if an `IoError` is expected.

This commit prepares the implementation of negative integration tests
for the configurable BDFs.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
2026-04-17 07:45:22 +00:00
Rob Bradford
b4723999f8 docs: Update the relevant documentation
Some of the documentation references PCI segment ID. For those documents
add a mention of the new PCI device ID.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Rob Bradford
5aa3692c6d vmm: device_manager: Reserve explicitly used PCI device IDs
Use two passes to first reserve PCI device IDs and then allocate them
when adding the devices to the bus. This prevents a situation where an
anonymous PCI device allocation clashes with an explicitly allocated PCI
device ID.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Pascal Scholz
aace90f270 vmm: Propagate PCI device ID from the config
We pass the device ID from the config to the allocation routine, where
it is then used as the preferred device ID alongside the existing PCI
segment ID.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Pascal Scholz
7315a38a02 vmm: Validate PCI device ID
Validate the PCI device ID are within range and not using the reserved
value. We need this option to ensure that invalid device IDs received
via an API call result in an error as soon as possible. In this case,
this would be after deserialization. On this code path, validation via
`parse` is skipped and must be invoked by calling `validate`.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Rob Bradford
4e247cf91d vmm: config: Add pci_device_id to SYNTAX for supported devices
For those devices types that have the the ability to support specifying
the PCI device ID add it to their help syntax.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Rob Bradford
e5d73159f6 vmm: openapi: Add pci_device_id to the required device entries
Also add pci_segment that was missing from vfio-user devices.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Rob Bradford
c3ec804a44 vmm: config: Add pci_device_id to PciDeviceCommonConfig
This adds it to all device types that use the common PCI device
configuration.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Rob Bradford
51a729a874 pci: Add support for reserving but not allocating slots
This can be used in a two pass approach where all configs that can hold
PCI devices are evaluated to reserve any specific PCI device IDs they
may need. Those device IDs will later be allocated when the devices are
added to the bus. The tri-state Free, Reserved, Allocated also catches
the problem of hotplugging a device with a specific, already used,
device ID.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Rob Bradford
3a5fad22b9 vmm: Fix segment log message formatting
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Pascal Scholz
93c17cb291 vmm: Add tests for allocate_device_id in PciSegment
Next to tests for `allocate_device_id`, we introduce a new constructor
`new_without_address_manager`, only available in the test build. As
there is no way to instantiate an `AddressManager` in the tests, we use
this constructor to work around this.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Pascal Scholz
34f08002e1 vmm: Allow for device ID allocation on a segment
Allocating a device ID is crucial for assigning a specific ID to a
device. We need this to implement configurable PCI device ID.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-17 07:45:22 +00:00
Pascal Scholz
afd155d578 pci: Refactor bus.rs to better fit a PCI bus's semantics
This commit refactors the PCI bus struct. It has two major focuses.
First, we change the type of `device_ids` in `PciBus` to an array. A
fixed-size array better reflects real PCI bus constraints, especially
its limited number of PCI devices. Moreover, it can't be grown
accidentally.

The second focus is changing the type of the key of `devices` in
`PciBus` to `u8`, since device IDs are not allowed to exceed 31. We
furthermore replace magic numbers with constants and make them publicly
available so we can use them in a follow-up change when parsing user
input.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
2026-04-17 07:45:22 +00:00
Rob Bradford
43b5a474f9 tests: Remove explicit sleep from test_api_dbus_and_http_interleaved
Instead wait for the guest to stop responding on the SSH port.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
1269475c2a tests: Remove explicit sleeps from pvpanic test
Instead wait for the event to be delivered that it has panicked.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
0107675eb1 tests: Remove explicit sleeps from net tests
Use `wait_until()` with the SSH command for detecting if the net device
is present/absent as part of hotplugging/unplugging.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
9ca6d4ca41 tests: Remove explicit sleeps from block tests
Use `wait_until()` with the SSH command for detecting if the block
device is present/absent as part of hotplugging/unplugging.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
78224baac6 tests: Remove explicit sleeps from virtio-fs tests
Instead use `wait_until()` for mounting of the filesystem.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
01decd964f tests: Remove explicit sleep from tests_simple_launch tests
On the shutdown path remove the explicit sleep and instead wait for
the event to be delivered.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
f55a90c170 tests: Remove explicit sleeps from "liveness" checks
The vhost-user tests uses SSH and checking the RAM to test for the
liveness of the VM - replace the explicit sleep before them with
`wait_until()` allowing them to potentially finish earlier.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
a1cfbd6f5c tests: Use wait_until() to check for vhost-user socket
Rather than use a fixed time to wait for the socket to be opened instead
test for its existence using `wait_until()`.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
52c24136b5 tests: Remove explicit sleeps before killing vhost-user daemons
If we're about to kill the daemons we don't need to spin waiting for
them.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
2eafee69be tests: Remove explicit sleeps from balloon tests
Use the new `wait_until()` to test the balloon size.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Rob Bradford
030e63476e tests: Reduce explicit sleep time in _test_api_* tests
Use new `wait_until()` and existing boot response mechanisms to remove
explicit sleeps from these tests.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-16 22:50:47 +00:00
Max Makarov
cdfedfaab2 vmm: device_manager: reject duplicate socket in add_user_device
Calling vm.add-user-device a second time with a socket path already
in use makes the VMM thread block indefinitely inside
vfio_user::Client::new(). libvfio-user servers (SPDK, the reference
libvfio-user daemon) accept a single active client per socket, so
the second connect(2) succeeds at the OS level but the handshake
recvmsg(2) waits for a response that never arrives.

All subsequent API requests queue behind the stuck VMM event loop
and also hang (vm.info, vmm.ping, vm.remove-device). The VM itself
keeps running on vcpu threads, making the symptom confusing: the
guest looks healthy, only the API is unreachable.

This is easy to hit from management software that uses an idempotent
reconcile / ensure pattern for user devices.

Reject the call up-front when another user_device already has the
same socket path, returning an HTTP 500 with a descriptive
UserDeviceSocketInUse error in milliseconds instead of hanging.

Signed-off-by: Max Makarov <maxpain@linux.com>
2026-04-16 22:04:38 +00:00
Keith Adler
e2c51042d3 vmm: preserve underlying errors in vm.rs instead of dropping them
Several error mappings in vm.rs dropped the underlying error with
map_err(|_| ...), making failures harder to diagnose. Preserve the
source error by adding #[source] fields to InitramfsLoad and ErrorNmi.

- InitramfsLoad: now wraps std::io::Error from seek/rewind operations
- ErrorNmi: now wraps cpu::Error from the CPU manager nmi() call

Partially addresses #7563

Signed-off-by: Keith Adler <kadler@cloudflare.com>
2026-04-16 20:48:19 +00:00
Anatol Belski
1ab877882d performance-metrics: Add QCOW2 batch write micro benchmark
Add micro_bench_qcow_batch_write which builds a batch of num_ops
write requests and submits them all at once through
submit_batch_requests. Writes in QcowAsync are synchronous (COW
path), so this measures whether batching reduces per-request
overhead compared to individual write_vectored calls.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
8052c5a66b performance-metrics: Add QCOW2 async L2 cache miss micro benchmark
Add micro_bench_qcow_async_l2_cache_miss which reads one cluster
from each of num_ops distinct L2 tables through the QcowAsync
io_uring path, forcing L2 cache eviction on nearly every read.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
3804968eef performance-metrics: Add sparse async QCOW2 tempfile helper
Add sparse_qcow_async_tempfile which creates a sparse QCOW2 image
with one cluster per L2 table and opens it via QcowDiskAsync.
Mirrors the existing sparse_qcow_tempfile for io_uring benchmarks.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
1d5d13eb7b performance-metrics: Add QCOW2 async write micro benchmark
Add micro_bench_qcow_async_write which writes clusters into an
empty QCOW2 image through the QcowAsync io_uring path. Writes
in QcowAsync are synchronous due to COW metadata allocation, so
this measures the write path overhead through the async code path.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
1069505151 performance-metrics: Add empty async QCOW2 tempfile helper
Add empty_qcow_async_tempfile which creates an empty QCOW2 image
and opens it via QcowDiskAsync. Mirrors the existing
empty_qcow_tempfile for io_uring write benchmarks.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
fca6429e9b performance-metrics: Add QCOW2 async compressed read micro benchmark
Add micro_bench_qcow_async_compressed_read which reads from a
zlib compressed QCOW2 image through the QcowAsync io_uring path.
Compressed clusters take the sync fallback since they require
decompression.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
4bf3672fad performance-metrics: Add compressed async QCOW2 tempfile helper
Add compressed_qcow_async_tempfile which creates a zlib compressed
QCOW2 image via qemu-img and opens it via QcowDiskAsync. Mirrors
the existing compressed_qcow_tempfile for io_uring benchmarks.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
324f16861d performance-metrics: Add QCOW2 async backing file read micro benchmark
Add micro_bench_qcow_async_backing_read which reads clusters from
a QCOW2 overlay through the QcowAsync io_uring path. All reads
fall through to the backing file, exercising the sync fallback
path in QcowAsync.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
c8fee5953f performance-metrics: Add async QCOW2 overlay tempfile helper
Add qcow_async_overlay_tempfile which creates a QCOW2 overlay
backed by a RAW file and opens it via QcowDiskAsync. Mirrors
the existing qcow_overlay_tempfile for io_uring benchmarks.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
2041ba5a91 performance-metrics: Add QCOW2 async multicluster read micro benchmark
Add micro_bench_qcow_async_multi_cluster_read which reads 8
contiguous clusters (512 KiB) per request through the QcowAsync
io_uring path. With coalesced mappings this can hit the io_uring
fast path for a single Readv SQE.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
377c260196 performance-metrics: Add QCOW2 async random read micro benchmark
Add micro_bench_qcow_async_random_read which reads clusters in
random order through the QcowAsync io_uring path. This mirrors
the existing sync random read benchmark and measures io_uring
completion handling under random access patterns.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
e9d1ffd24f performance-metrics: Add QCOW2 batch read micro benchmark
Add micro_bench_qcow_batch_read which builds a batch of num_ops
read requests and submits them all at once through
submit_batch_requests. This exercises the io_uring batch
submission path added in qcow_async, where multiple SQEs are
packed into a single io_uring_enter call.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
f8dbec0abb performance-metrics: Add QCOW2 async read micro benchmark
Add micro_bench_qcow_async_read which reads clusters through the
QcowDiskAsync io_uring backend. Single allocated cluster reads go
through io_uring for true asynchronous completion, unlike the sync
benchmarks which use QcowDiskSync with blocking I/O.

Workloads: 128 and 256 clusters.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
3cb116fcfc performance-metrics: Add async drain completions helper
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
faf6f7b635 performance-metrics: Add QCOW2 L2 cache cold miss micro benchmark
Add micro_bench_qcow_l2_cache_miss which reads one cluster from each
of num_ops distinct L2 tables in a sparsely allocated image.  Clusters
are spaced L2_ENTRIES_PER_TABLE apart so every read touches a different
L2 table, forcing eviction when num_ops exceeds the cache capacity.

Workloads: 128 and 256 L2 tables.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
73d99c044c performance-metrics: Add sparse QCOW2 tempfile helper
Add sparse_qcow_tempfile() which creates a QCOW2 image with one
allocated cluster per L2 table, spread across num_l2_tables distinct
L2 tables.  Reading these clusters in sequence forces L2 cache misses
when the count exceeds the cache capacity.

Also add the L2_ENTRIES_PER_TABLE constant, 8192 for 64 KiB clusters.

To be used by the L2 cache cold miss benchmark.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
94f78edcf0 performance-metrics: Add QCOW2 multi-cluster read micro benchmark
Add micro_bench_qcow_multi_cluster_read which issues large reads
spanning 8 contiguous clusters (512 KiB) per read_vectored call.
This exercises the mapping coalesce path where multiple L2 entries
are merged into fewer host I/O operations.

Workloads: 128 and 256 total clusters (16 and 32 reads).
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
7dd1978fce performance-metrics: Add QCOW2 compressed read micro benchmark
Add micro_bench_qcow_compressed_read which reads clusters from a
zlib compressed QCOW2 image. Every cluster triggers decompression,
isolating the decompression overhead from the normal allocated cluster
read path.

Workloads: 128 and 256 clusters.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
aca64ced8f performance-metrics: Add compressed QCOW2 tempfile helper
Add compressed_qcow_tempfile() which creates a zlib compressed QCOW2
image by populating a RAW tempfile with data and converting it via
qemu-img convert -c.  Every cluster in the resulting image is stored
compressed so reads exercise the decompression path.

To be used by the compressed read benchmark.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
b2430d701b performance-metrics: Add QCOW2 copy-on-write write micro benchmark
Add micro_bench_qcow_cow_write which writes clusters into a QCOW2
overlay backed by a raw file.  Each write triggers copy-on-write:
cluster allocation, L2 and refcount table updates, then the data
write.  This measures COW allocation overhead compared to writing
into a plain empty image.

Workloads: 128 and 256 clusters.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
79b58c0faf performance-metrics: Add QCOW2 backing file read micro benchmark
Add micro_bench_qcow_backing_read which reads clusters from a QCOW2
overlay where all data lives in a raw backing file.  Every read falls
through the L2 lookup to the backing file, exercising the backing
chain read path.

Workloads: 128 and 256 clusters.
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
bdce007aac performance-metrics: Add QCOW2 overlay tempfile helper
Add qcow_overlay_tempfile() which creates a raw backing file with
pre-populated data and a QCOW2 overlay on top with no allocated
clusters.  The overlay is opened with backing file support via
QcowDiskSync so reads fall through to the backing file.

To be used by backing file read and copy-on-write write
benchmarks.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
99b4320279 performance-metrics: Add qcow2 random read micro benchmark
Add micro_bench_qcow_random_read which reads clusters from a
prepopulated qcow2 image in a deterministic pseudo-random order.
Unlike the sequential read benchmark, this exercises L2 cache miss
and eviction behaviour under random access patterns.

Uses Fisher-Yates shuffle with DefaultHasher for reproducible
permutation across runs.

Two TEST_LIST entries: micro_block_qcow_random_read_128_us and
micro_block_qcow_random_read_256_us with 128 and 256 cluster
workloads.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
638cb3d7f2 performance-metrics: Add deterministic permutation helper
Add deterministic_permutation() which produces a reproducible
pseudo random permutation of [0, n) using a Fisher-Yates shuffle
seeded by DefaultHasher. This is used by the random read micro
benchmarks to generate a fixed access pattern that is identical
across runs.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
e802c0d8b9 performance-metrics: Add qcow2 fsync micro benchmark
Add micro_bench_qcow_fsync which writes num_ops clusters into an
empty qcow2 image to dirty L2 and refcount metadata then times a
single fsync call that flushes all dirty tables to disk. This
isolates the metadata flush cost which scales with the number of
dirty L2 table entries and refcount blocks.

Two TEST_LIST entries: micro_block_qcow_fsync_64_us and
micro_block_qcow_fsync_256_us with 64 and 256 cluster workloads.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
c3312dca3f performance-metrics: Add qcow2 punch hole micro benchmark
Add micro_bench_qcow_punch_hole which times punch_hole calls through
QcowSync on a prepopulated qcow2 image. Each call deallocates one
cluster exercising deallocate_bytes with refcount decrement and
fallocate punch_hole on the host file.

Two TEST_LIST entries: micro_block_qcow_punch_hole_64_us and
micro_block_qcow_punch_hole_256_us with 64 and 256 cluster workloads.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
f7e40eec9a performance-metrics: Add QCOW2 write micro benchmark
Add micro_bench_qcow_write which times write_vectored calls through
QcowSync on an empty QCOW2 image. Each write allocates a new cluster
exercising map_cluster_for_write with L2 entry allocation and refcount
updates followed by pwrite_all.

Two TEST_LIST entries: micro_block_qcow_write_128_us and
micro_block_qcow_write_256_us with 128 and 256 cluster workloads.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
9f317895b5 performance-metrics: Add submit_writes helper
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
0854c3e082 performance-metrics: Add empty QCOW2 tempfile helper
Add empty_qcow_tempfile() which creates a QCOW2 v3 image with no
allocated clusters so every write triggers the full cluster allocation
path including L2 entry allocation and refcount updates.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
6cd3395a55 performance-metrics: Add QCOW2 read micro benchmark
Add micro_bench_qcow_read which times read_vectored calls through
QcowSync on a prepopulated QCOW2 image. This exercises the hot
read path including L2 lookup, pread64 for allocated clusters and
iovec scatter.

Two TEST_LIST entries: micro_block_qcow_read_128_us and
micro_block_qcow_read_256_us with 128 and 256 cluster workloads.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
beaa98728c performance-metrics: Add iovec construction helpers
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
f8deeb8a1c performance-metrics: Add submit_reads helper
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
97b109bc89 performance-metrics: Add sync drain completions helper
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
ab1dddb62a performance-metrics: Add QCOW2 tempfile helpers
Add qcow_tempfile() which creates a QCOW2 v3 image with all clusters
allocated via QcowFile::new plus sequential writes, then reopens it
as QcowDiskSync. Add QCOW_CLUSTER_SIZE constant for the default
64 KiB cluster size.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Anatol Belski
4a607ed82d performance-metrics: Enable io_uring feature on block crate
Enable the io_uring feature so that QcowDiskAsync and QcowAsync are
available for async path micro benchmarks.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-16 17:27:38 +00:00
Dylan Reid
c1b4fcc750 virtio-devices: More detailed vhost user errors
Make it easier to chase down which vhost user socket failed and why in
systems that have many vhost user devices.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-16 19:18:00 +01:00
Keith Adler
07b77b0f4b ci: remove pinned cross version from quality.yaml
Remove the pinned cross-version commit hash from all
houseabsolute/actions-rust-cross usages. The pin was added as a
workaround for virtio-bindings build issues that have since been
resolved upstream.

Closes #7180

Signed-off-by: Keith Adler <kadler@cloudflare.com>
2026-04-16 14:32:48 +01:00
Anirudh Rayabharam
c7a152ee79 performance-metrics: fix overly broad process cleanup
Drop the -f flag from the process termination command in
cleanup_stale_processes() so it matches by process name only, not the
full command line. This prevents terminating unrelated processes whose
arguments happen to contain target strings (e.g., the test runner
invoked with --report-file /cloud-hypervisor/report.json).

Use the truncated name 'cloud-hyperviso' because Linux limits process
names to 15 characters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-04-16 14:12:48 +01:00
Leander Kohler
005ce38ffd main: add --no-shutdown
Add a CLI-only --no-shutdown flag that keeps the VMM process alive
after a guest-triggered shutdown.

Management software may still need the Cloud Hypervisor process
after the guest has powered off. Exposing this separately lets
management software, for example libvirt, keep the VMM around in a
way that is closer to QEMU.

The flag only affects the GuestExit path. Fatal exits and other
existing VMM shutdown paths remain unchanged.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-04-15 17:58:56 +00:00
Leander Kohler
a159152e41 devices: route guest shutdown via guest exit
Plumb ACPI S5 shutdown through guest_exit_evt instead of the shared
exit path.

This keeps guest-triggered shutdown separate from fatal VMM exit
handling. Management software, for example libvirt, expects that
distinction, and making it explicit aligns Cloud Hypervisor more
closely with QEMU.

Only the guest shutdown path is moved here. Reboot handling stays on
reset_evt and non-guest exit paths are left unchanged.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-04-15 17:58:56 +00:00
Leander Kohler
c698075157 vmm: add guest exit event path
Introduce a dedicated guest_exit_evt and a matching epoll dispatch
path for guest-triggered shutdowns.

This series is needed because managment software such as libvirt may
still need the Cloud Hypervisor process to stay alive after the guest
has shut down.
Today a guest-triggered shutdown can make the VMM disappear immediately,
which means the managment software can lose track of the VM run-state.

This must only apply to guest-triggered shutdowns. Fatal error paths
and other internal exit paths must keep using the existing VMM exit
handling.

For now GuestExit still calls vmm_shutdown(), so this commit only adds
the separate plumbing and keeps the current behavior unchanged.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-04-15 17:58:56 +00:00
Anatol Belski
dc0e003be0 block: qcow: Add AlignedBuf size rounding test
Verify that AlignedBuf rounds the allocation size up to the
requested alignment. Passes under miri.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
a84a0b8b25 block: qcow: Add AlignedBuf allocation and access test
Test AlignedBuf with 512 and 4096 byte alignment. Verify pointer
alignment, zero initialization, and write/read round trip. Passes
under miri.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
11cf332114 tests: Add Windows QCOW2 guest boot with direct I/O test
Boot a Windows guest from a qcow2 overlay with direct=on. After
boot, write 5 randomly filled files from 4MB to 20MB, copy each
file, and compare SHA256 hashes to verify data integrity through
the aligned bounce buffer path.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
e5ad85d7de test_infra: Prepare QCOW2 overlay for Windows guests
WindowsDiskConfig now creates a qcow2 overlay backed by the raw
Windows image during prepare_files(). The overlay is placed under
~/workloads alongside the raw image. Writes go into the overlay
so the backing raw image stays unmodified, matching the CoW
semantics already provided by the dm snapshot for raw tests.

Drop removes the qcow2 file.

The DiskConfig trait gains a qcow2_disk() default method returning
None. WindowsDiskConfig overrides it to expose the overlay path.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
ab81112618 block: qcow: Test aligned pread and pwrite with 4096 alignment
Exercise both aligned_pread and aligned_pwrite with 4096 byte
alignment instead of 512. Verify written data and that surrounding
regions are preserved.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
7aa477936e block: qcow: Test aligned_pwrite unaligned offset
Write at offset 100 with alignment 512 so the read modify write
path is exercised. Verify the written region and that surrounding
data is preserved.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
5ed9f2e3d8 block: qcow: Test aligned_pwrite bounce unaligned buffer
Write 4096 bytes via plain Vec<u8> whose address is not guaranteed
to be aligned. The bounce buffer path copies data into an aligned
allocation before the syscall. Read back with pread_exact to verify
data integrity.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
32af2f2a22 block: qcow: Test aligned_pwrite pass through path
Write 4096 bytes of pattern data at offset 0 using AlignedBuf
and verify data integrity via plain pread_exact. All parameters
are naturally aligned to 512 so the fast path is exercised.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
05d52d0353 block: qcow: Add aligned_pread unaligned offset test
Test that aligned_pread handles a non aligned offset by
rounding down, reading an aligned region, and returning the
correct slice from within the bounce buffer.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
021838b63c block: qcow: Add aligned_pread bounce buffer test
Test that aligned_pread correctly uses a bounce buffer when
the caller buffer address is not aligned.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
fe711b3a0b block: qcow: Add aligned_pread pass through test
Test that aligned_pread takes the fast path when buffer
address, length, and offset are all properly aligned.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
7fd5e74f0e block: qcow: Add multi iovec read/write test
Exercise scatter/gather with multiple iovecs per operation,
covering both the standard and direct_io paths. Write uses
3 iovecs with distinct patterns, read uses 3 iovecs with
different sizes, then reassembles and compares.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
b62525f792 block: qcow: Add direct_io test coverage for QcowSync
Add direct_io variants for suitable tests by extracting
test bodies into _impl(direct_io: bool) functions. Each
original test calls _impl(false) and a new _direct_io test
calls _impl(true).

When direct_io is true, RawFile probes alignment and QcowSync
exercises the AlignedBuf and bounce buffer paths in
read_vectored and write_vectored.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
cfa60a95b9 block: qcow: Use aligned I/O in QcowAsync
Store the alignment from the data file in QcowAsync. Use
aligned_pread in scatter_read_sync and aligned_pwrite with
gather_from_iovecs_into in cow_write_sync, matching the
QcowSync approach.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
fd8495b342 block: qcow: Use aligned I/O in QcowSync
Store the alignment from the data file in QcowSync. Use AlignedBuf
directly in read_vectored and write_vectored as the intermediate
buffer so that aligned_pread/aligned_pwrite can skip the bounce
copy when offset and length are naturally aligned.

Use gather_from_iovecs_into to gather iovec data directly into the
aligned buffer.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
CMGS
f50b5ab1f2 block: qcow: Add aligned bounce buffers for O_DIRECT I/O
When the data file is opened with O_DIRECT, buffer address, length,
and file offset must satisfy the device alignment.

Add AlignedBuf RAII wrapper and aligned_pread/aligned_pwrite helpers
in qcow_common that use bounce buffers when alignment constraints
are not met. For writes with misaligned offset, a read modify write
is performed on the aligned region.

gather_from_iovecs_into gathers iovec data directly into a caller
provided buffer, avoiding an intermediate Vec allocation.

Fixes: #8007
Signed-off-by: CMGS <ilskdw@gmail.com>
Co-authored-by: Anatol Belski <anbelski@linux.microsoft.com>
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-15 17:28:58 +00:00
Anatol Belski
6c1da4f5c1 block: qcow: Expose RawFile alignment as a public accessor
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>
2026-04-15 17:28:58 +00:00
Anatol Belski
c35749fb39 block: qcow: Rename Qcow2MetadataBacking to Qcow2Backing
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>
2026-04-15 14:40:14 +00:00
Philipp Schuster
39844e8839 docs: refine coding standards in CONTRIBUTING.md
TL;DR: Add note about how we expect code comments/documentation

This updates the coding standards as discussed [0]. The general
guideline is to write down as little process as possible and leave room
for pragmatic exceptions, maintainer and contributor preferences while
still striving for excellent code quality.

[0]: https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7990#issuecomment-4245571054

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-15 15:06:46 +01:00
Philipp Schuster
edfd597993 tests: fix weird "console=ttyS0rw" string
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-15 11:57:23 +00:00
Philipp Schuster
7eab5901ad vmm: improve misc documentation
This improves the documentation at various places.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-15 11:57:23 +00:00
Philipp Schuster
5ff4696cea vmm: introduce ACPI CPU hotplug controller (fix deadlock)
Extract AcpiCpuHotplugController from CpuManager and move the BusDevice
implementation to the new type. This separates VMM-internal vCPU
management from the guest-visible ACPI CPU hotplug MMIO interface.

Besides clarifying responsibilities and reducing technical debt, this
fixes a rare deadlock involving pause handling and MMIO access.

New responsibilities:
- CpuManager manages VMM-internal vCPU lifecycle and coordination
- AcpiCpuHotplugController implements the guest-visible ACPI CPU hotplug
  MMIO interface

A vCPU thread may exit KVM_RUN to perform an MMIO access previously
handled by CpuManager. If the VMM thread begins processing a `pause`
event before that MMIO operation acquires access to CpuManager,
CpuManager::pause() will block waiting for the vCPU thread to ACK
the pause, while the vCPU thread is blocked waiting to complete the MMIO
operation through the same CpuManager - which it can never lock - the
VMM is deadlocked.

This can occur during early boot or CPU hotplug when pause events race
with MMIO accesses. The issue is rare and timing-dependent, but real.
For reproducing: run `ch-remote pause|resume` in a loop while booting
a Linux VM (via direct kernel boot).

With the new design, these MMIO operations no longer depend on
CpuManager, which removes the deadlock path entirely.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-15 11:57:23 +00:00
Philipp Schuster
6d0d4bc5e2 vmm: protect vcpu states in CpuManager with a mutex
This is a prerequisite for the next commit where we need shared access.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-15 11:57:23 +00:00
dependabot[bot]
c657ea6e23 build(deps): bump softprops/action-gh-release from 2 to 3
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-15 00:43:47 +00:00
Dylan Reid
a6d3901f3e misc: return errors from IOMMU address translation instead of panicking
The address that is passed from the guest should be treated as
untrusted. Currently an invalid address will panic the VMM. This only
allows the guest to hurt itself, but we shouldn't have the VMM crashing.
Instead let's return an error if possible or invalidate the queue if it
happen during setup.

The data flow from guest to translate_gva/translate_gpa is:

  1. Guest writes a raw u64 address into a virtio descriptor in the
     shared descriptor table (guest memory).
  2. The virtio-queue crate reads this descriptor via read_obj() and
     returns the addr field as-is in a GuestAddress — no validation.
  3. Device code calls .translate_gva(access_platform, len) on the
     GuestAddress.
  4. With IOMMU (access_platform is Some): the address is an IOVA that
     must be translated to a GPA via the IOMMU mapping table. If the
     guest provides an unmapped IOVA, translation returns Err.
     Previously, .unwrap() here panicked the VMM.
  5. Without IOMMU (access_platform is None): translate_gva is a no-op
     (returns self). The raw address flows to GuestMemory::read_obj()
     which validates it — out-of-range addresses return
     Err(InvalidGuestAddress), so no host memory corruption is possible.

Signed-off-by: Dylan Reid <dgreid@fb.com>
2026-04-14 23:25:03 +00:00
Anatol Belski
d4fc1d38c8 scripts: dev_cli: Allow io_uring syscalls in unit tests
The unit test container runs with Docker default seccomp
profile which blocks io_uring_setup, io_uring_enter and
io_uring_register. This causes all qcow_async unit tests to
fail with EPERM when creating an io_uring instance.

Add --security-opt seccomp=unconfined to the unit test docker
run invocation. The container already has --device access and
cap_net_admin, so this does not materially change the security
posture.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
bb833a90e5 block: qcow_async: Add large sequential I/O test
Write a distinct byte pattern into each of eight consecutive
clusters in a single operation, then read the full range back
and verify per cluster contents.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
072b3a85d2 block: qcow_async: Add write after punch hole test
Write data, punch hole to deallocate, then rewrite the same
range and verify the new contents read back correctly.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
48155c4151 block: qcow: Test async sub cluster write
Add a QcowAsync unit test that writes 4K into the middle of a
cluster, then reads the entire cluster back. Verifies that the
written region matches and surrounding bytes remain zero. This
exercises the COW path where unwritten parts of a newly allocated
cluster must be zero filled.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
6208f3caee block: qcow: Test async read of unallocated region
Add a QcowAsync unit test that reads from a range that was never
written. Verifies the fundamental QCOW contract that unallocated
clusters return zeroes.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
34a9f7246d block: qcow: Test async batch mixed requests
Add a QcowAsync unit test that submits a batch of interleaved
write and read requests via submit_batch_requests. Verifies that
all completions arrive with the correct user_data and that the
read back data matches the written data.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
093922ff24 block: qcow: Test async read spanning cluster boundary
Add a QcowAsync unit test that writes distinct patterns into two
adjacent clusters, then issues a single read spanning the cluster
boundary. Verifies that multi mapping read resolution returns the
correct data from both clusters.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
1e66c144f4 block: qcow: Test async write and read roundtrip
Add a QcowAsync unit test that writes a byte pattern through
write_vectored, reads it back through read_vectored, and verifies
the data matches. This exercises the core async write and read
paths end to end.

Also add an async_write helper for future tests.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
e522d3a0aa block: qcow: Test async write zeroes
Add a QcowAsync unit test for write zeroes completion. The test
verifies that a write zeroes request reports successful completion
and that the zeroed range reads back as zeroes.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
8ffbe9d6fe block: qcow: Test async punch hole
Add a QcowAsync unit test for punch hole completion. The test
verifies that a punch hole request reports successful completion
and that the deallocated range reads back as zeroes.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
b1d126fdbf block: qcow: Implement batch request submission
Implement batch_requests_enabled() and submit_batch_requests() for
QcowAsync. Without batching, each read_vectored call performs its own
io_uring submit() syscall. With batching, the virtio queue handler
collects all pending requests and submits them in a single call,
pushing multiple SQEs before one submit() syscall.

Each request in the batch is classified through the metadata layer.
Requests that hit the fast path (single allocated cluster mapping)
are pushed to the io_uring submission queue. Requests that require
the slow path (compressed, backing, zero fill, or mixed mappings)
are completed synchronously and queued as synthetic completions.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
3d5a40dfa6 vmm: device_manager: Wire up QcowDiskAsync with io_uring
When io_uring is available and not disabled, open QCOW2 images
with QcowDiskAsync for asynchronous reads. Falls back to
QcowDiskSync otherwise.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
ffc579b913 block: qcow_async: impl AsyncDiskFile for QcowDiskAsync
try_clone shares the Arc wrapped metadata and backing file.
new_async_io creates a QcowAsync worker with its own io_uring
instance for the given ring depth.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
261361c1b0 block: qcow_async: impl punch_hole and write_zeroes for QcowAsync
Deallocate clusters through QcowMetadata::deallocate_bytes, then
apply the resulting DeallocAction list (punch hole or write zeroes
at host offsets). write_zeroes delegates to punch_hole since
unallocated QCOW2 clusters inherently read as zero.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
bf70c19857 block: qcow_async: impl fsync for QcowAsync
Flush dirty metadata caches and sync the underlying file via
QcowMetadata::flush, then signal synthetic completion.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
a97260c5e0 block: qcow_async: impl write_vectored for QcowAsync
Synchronous per cluster write path - gather guest data from iovecs,
map each cluster through QcowMetadata, and pwrite to the allocated
host offset. Partial cluster writes with a backing file read the
backing data first so map_cluster_for_write can perform COW.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
e7621abfbd block: qcow_async: impl read_vectored for QcowAsync
Single allocated cluster reads are submitted to io_uring for true
async completion. Mixed mapping reads (zero, compressed, backing,
multi cluster) fall back to synchronous pread64 with synthetic
completions.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
85df0fef0d block: qcow_async: impl AsyncIo scaffold for QcowAsync
Add the AsyncIo trait impl with notifier and next_completed_request
filled in. The remaining methods are stubbed with unimplemented
and will be filled in by subsequent commits.

next_completed_request drains io_uring completions first, then
falls back to the synthetic completion list.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
8d684cad98 block: qcow_async: Add QcowAsync struct and constructor
Per queue I/O worker that uses io_uring for asynchronous reads
against fully allocated clusters. The struct holds the shared
metadata, data file, optional backing reader, the io_uring
instance and a synthetic completion list.

Feature gated on io_uring in lib.rs.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
8966607898 block: qcow_async: impl DiskFile for QcowDiskAsync
Marker supertrait combining all composable capability traits.
QcowDiskAsync now satisfies the full DiskFile contract.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
8c3b6cb04b block: qcow_async: impl Resizable for QcowDiskAsync
Delegates to QcowMetadata::resize. Rejects resize when a backing
file is present, same as the sync backend.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
52cd45874a block: qcow_async: impl SparseCapable for QcowDiskAsync
QCOW2 images support both sparse operations and the zero flag.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
fa700f6493 block: qcow_async: impl Geometry for QcowDiskAsync
Uses the default geometry, same as the sync backend.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
91d6356db4 block: qcow_async: impl DiskFd for QcowDiskAsync
Returns a borrowed file descriptor for the underlying data file.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
403b75656c block: qcow_async: impl PhysicalSize for QcowDiskAsync
Queries the underlying raw file for on disk size.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
f777113818 block: qcow_async: impl DiskSize for QcowDiskAsync
Delegates to QcowMetadata::virtual_size, identical to the sync
backend.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
3422a8b258 block: qcow: Add QcowDiskAsync struct stub
Introduce the device level handle for the async QCOW2 backend.

QcowDiskAsync mirrors QcowDiskSync. It parses the image, resolves
the backing chain and wraps QcowMetadata in an Arc for sharing
across virtio queues. No trait impls yet, just the struct,
constructor, Drop and Debug.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
8cd2c957ef block: qcow: Move shared_backing_from to qcow/backing
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>
2026-04-14 22:11:26 +00:00
Anatol Belski
5ec80d45ab block: qcow: Move Qcow2MetadataBacking to qcow/backing
Move the QCOW2 metadata backed reader into qcow/backing alongside
RawBacking.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
e345299f4d block: qcow: Move RawBacking to qcow/backing module
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>
2026-04-14 22:11:26 +00:00
Anatol Belski
81f43f96c3 block: qcow: Move iovec scatter/gather helpers to qcow_common
Move scatter_to_iovecs, zero_fill_iovecs and gather_from_iovecs into
qcow_common so they can be shared with the upcoming qcow_async backend.

These helpers treat an iovec array as a flat byte stream and are used by
both read_vectored and write_vectored code paths.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
7f3dfe2154 block: qcow: Extract positional I/O helpers into a common module
These position independent I/O helpers use pread64/pwrite64 to avoid
races on the shared file position when multiple queues operate on
duplicated file descriptors. Extracting them prepares for reuse by
the upcoming qcow_async backend.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 22:11:26 +00:00
Anatol Belski
73680c38c7 ci: Switch to Windows Server 2025 for AMD64
The updated image is configured in a same way as the
previously used 2022.

SAC, SSH, and RDP are configured.

All Windows updates to the curent date are installed.

Includes latest stable virtio-win 0.1.285 drivers.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-14 16:09:36 +00:00
Philipp Schuster
98aa9d9c12 tests: make VFIO memory hotplug more robust
After memory hotplug, it may happen that it takes a few seconds until a
VFIO device is available again (IOMMU/DMA mappings need update).

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 15:11:42 +00:00
Philipp Schuster
e6c8b5e816 tests: run more tests in parallel
They can safely run in parallel. This further speeds up the CI by ~5.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 15:11:42 +00:00
Rob Bradford
cc7e56fa07 vmm: device_manager: Use more idiomatic Rust for ID assignment
Use a more idiomatic Rust approach when establishing an autogenerated ID
when none is set.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
7ac877cc26 vmm: device_manager: Reuse PciDeviceCommonConfig in MetaVirtioDevice
This struct has the same members and it can be reused to reduce
complexity now and if other common PCI related fields need to be
added in the future.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
0c837abff2 vmm: config: Put common options in an array
This can then be used with the OptionParser::add_all() API to reduce the
number of locations the same options are added to the parser. The only
quirk is that some devices do not support an IOMMU (because they are
vhost-user / vfio-user based). There are two different versions of the
array to support that.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
dde28dc38a vmm: config: Remove unused error variant
The IommuNotSupportedOnSegment variant is no longer needed as the common
PciDeviceCommonConfig::validate() handles this case with the
OnIommuSegment variant along with more use of the IommuNotSupported
error variant.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
37b1ed1b84 vmm: config: Switch VsockConfig to use PciDeviceCommonConfig
Switch VsockConfig over to using the newly extracted struct members as
used by all PCI based devices. The use of #[serde(flatten)] means that
this change has no impact on the JSON format that the data is stored as.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
ece77c3c52 vmm: config: Switch VdpaConfig to use PciDeviceCommonConfig
Switch VdpaConfig over to using the newly extracted struct members as
used by all PCI based devices. The use of #[serde(flatten)] means that
this change has no impact on the JSON format that the data is stored as.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
2c50be4753 vmm: config: Switch UserDeviceConfig to use PciDeviceCommonConfig
Switch UserDeviceConfig over to using the newly extracted struct members
as used by all PCI based devices. The use of #[serde(flatten)] means
that this change has no impact on the JSON format that the data is
stored as.

As VFIO user devices do not support being placed behind an IOMMU an
error is now raised if iommu is set. This can't happen via the CLI but
could via the JSON/API.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
47182201f9 vmm: config: Switch DeviceConfig to use PciDeviceCommonConfig
Switch DeviceConfig over to using the newly extracted struct members as
used by all PCI based devices. The use of #[serde(flatten)] means that
this change has no impact on the JSON format that the data is stored as.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
c9082570b0 vmm: config: Switch PmemConfig to use PciDeviceCommonConfig
Switch PmemConfig over to using the newly extracted struct members as
used by all PCI based devices. The use of #[serde(flatten)] means that
this change has no impact on the JSON format that the data is stored as.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
0d3080e036 vmm: config: Switch GenericVhostUserConfig to use PciDeviceCommonConfig
Switch GenericVhostUserConfig over to using the newly extracted struct
members as used by all PCI based devices. The use of #[serde(flatten)]
means that this change has no impact on the JSON format that the data is
stored as.

As generic vhost-user devices do not support being placed behind an
IOMMU an error is now raised if iommu is set. This can't happen via the
CLI but could via the JSON/API.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
40150dd72d vmm: config: Switch FsConfig to use PciDeviceCommonConfig
Switch FsConfig over to using the newly extracted struct members as used
by all PCI based devices. The use of #[serde(flatten)] means that this
change has no impact on the JSON format that the data is stored as.

As virtio-fs does not support being placed behind an IOMMU an error is
now raised if iommu is set. This option is not exposed via the CLI but
could happen with a miscontructed JSON/API call.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
92c2cf0103 vmm: config: Switch NetConfig to use PciDeviceCommonConfig
Switch NetConfig over to using the newly extracted struct members as
used by all PCI based devices. The use of #[serde(flatten)] means that
this change has no impact on the JSON format that the data is stored as.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
d2ce7667bc vmm: config: Switch DiskConfig to use PciDeviceCommonConfig
Switch DiskConfig over to using the newly extracted struct members as
used by all PCI based devices. The use of #[serde(flatten)] means that
this change has no impact on the JSON format that the data is stored as.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
a96da9d4bc vmm: Introduce PciDeviceCommonConfig::validate()
Implement some common PCI segment validation. This can be used to reduce
duplication across the different validation methods.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
c66c2b8470 vmm: config: Implement PciDeviceCommonConfig::parse
This parses a subset of the device configuration options used for
devices that are PCI based.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
4f7ff8fe48 vmm: Introduce a PciDeviceCommonConfig struct
Introduce a common struct that can encompass all the config fields
used by devices that are PCI based. The use of `skip_serializing_if`
means that the iommu field will only be included if set (otherwise
falling back to default false). This neatly handles the devices that
don't support an iommu.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
2148f2e0bc option_parser: Fix incorrect unit test
This unit test was trying to test with extra "="s in the input but was
instead testing using an unknown option. Add the option to the parser to
not hit that incorrect error.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
d93770c11d option_parser: Fill out unit testing
Add unit tests generated with Claude Opus 4.6 and reviewed by human
eyes.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
e5496a093d option_parser: Fix incorrect error message
The error message for the InvalidSyntax was copied from UnknownOption.
Correct it to "invalid syntax".

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
d212255073 option_parser: Add documentation strings
Autogenerated with Claude Opus 4.6 and reviewed with human eyes.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
791889cefd option_parser: Add support for adding from a slice
Add an OptionParser::add_all method that takes a slice of option names
and use that to add to the set of parameters that the parser works on.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Rob Bradford
a8ff2c50af option_parser: Introduce parse_subset() tolerating unknown options
Refactor parse() into a version that can control whether to tolerate
unknown options. This can then be used to then parse a subset of the
options.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 14:19:34 +00:00
Nguyen Dinh Phi
1268539b26 main: remove api socket path when start_vmm fails
Previously the UNIX socket file was only removed on the success path
(start_vmm returned Ok(Some(path))). If start_vmm failed after the
HTTP API had bound a path-based socket, the file could be left on disk.
Parse --api-socket in parse_api_socket(), call start_vmm with the
result, then unlink the path in main after start_vmm returns for both
success and failure (fd= mode unchanged: no path to remove).

Signed-off-by: Nguyen Dinh Phi <phind.uet@gmail.com>
2026-04-14 14:18:16 +00:00
Shayon Mukherjee
0a32a9ca91 vmm: add compile-time validation for userfaultfd ioctl constants
Cross-check each UFFDIO_* constant against the Linux _IOC(dir, type,
nr, size) encoding formula at compile time so that transposed direction
bits or struct sizes are caught immediately rather than silently
producing wrong ioctl numbers at runtime.

Signed-off-by: Shayon Mukherjee <shayonj@gmail.com>
2026-04-14 14:17:40 +00:00
Rob Bradford
72fc0976f1 virtio-devices: net: Remove "driver_awake" workaround for restore
Now on the generic restore path the worker thread is notified on the
events and also the guest is notified via the interrupt. This avoids the
same "livelock" situation that required this "driver_awake" workaround
when restoring the net device.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 12:29:31 +00:00
Rob Bradford
101c259051 virtio-devices: trigger interrupt into guest on resume
This will wake up the guest and avoid a livelock situation by ensuring
that it will process any pending queues on its side.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-14 12:29:31 +00:00
Leander Kohler
3e3008f365 virtio-devices: signal activated queue eventfds on resume
A restored virtqueue can already contain pending descriptors when the VM
resumes. Before this change, the worker thread was unparked and then
waited for a fresh queue eventfd signal. That is normally fine, but not
when the queue was already non-empty at snapshot time. The virtqueue
state lives in guest memory and is restored, but the original host-side
queue eventfd signal is not persistent snapshot state. If the guest
already notified the queue before the snapshot, it may not notify it
again after resume.

That can leave the worker idle while the guest is still waiting for the
pending request to complete. In one observed case, this stalled a
virtio-blk flush during early boot after snapshot/restore.

We mitigate this in the shared `VirtioCommon` resume path.
`VirtioCommon` retains cloned queue eventfds for activated virtqueues
and signals each of them once on resume after unparking the worker
threads.

Keep virtio-net on its existing special-case path: it resumes worker
threads without signaling queue eventfds so the `driver_awake`
workaround remains intact until the guest performs a real notify.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-04-14 12:29:31 +00:00
Zhiheng Tao
df58e814eb vmm: fix UFFDIO_WAKE and UFFD_FEATURE_MISSING_HUGETLBFS
UFFDIO_WAKE was 0x4010_aa02 (_IOW) but should be 0x8010_aa02,
causing every wake call to silently fail with -EINVAL.

UFFD_FEATURE_MISSING_HUGETLBFS was (1<<6) but should be (1<<4),
colliding with UFFD_FEATURE_EVENT_UNMAP.

Signed-off-by: Zhiheng Tao <junchuan.tzh@antgroup.com>
2026-04-14 11:20:32 +00:00
Philipp Schuster
d54a7d27a1 tests: Reduce memory usage in integration tests
This reduces pressure on CI and enables to run more tests locally on
developer machines (with 16GB of RAM or less).

No functional changes.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 09:18:47 +00:00
Philipp Schuster
cd92f65f7b tests: Replace Windows integration sleeps with polling
Replace fixed sleeps in Windows integration tests with polling
helpers that wait for boot, snapshot readiness, and device
enumeration.

This keeps the same test intent while avoiding long fixed delays on
the fast path.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 09:18:47 +00:00
Philipp Schuster
44ac7c0ba0 tests: Poll snapshot restore readiness in Linux tests
Replace fixed sleeps in Linux snapshot and restore integration tests
with event monitor and API readiness checks.

This updates ivshmem and common_sequential snapshot paths to wait for
concrete restore and snapshot completion signals instead of sleeping
for an assumed amount of time.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 09:18:47 +00:00
Philipp Schuster
47024e73ce tests: Replace common integration sleeps with polling
Use polling helpers in common integration tests instead of fixed
sleeps where the tests already know the expected ready state.

This updates CPU and memory hotplug checks as well as a few
device- and restore-related waits in common_parallel to stop
oversleeping on the fast path while keeping the same assertions.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 09:18:47 +00:00
Philipp Schuster
2c395d4ae8 tests: Plumping to retry when event monitor output is not ready
Treat missing or still-short event monitor files as a retryable state
in integration test helpers.

This keeps polling-based restore and snapshot checks from failing early
with file-not-found or short-file assertions while the monitor output
is still being written.

In the following, we can gracefully wait for the corresponding
conditions to become true.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 09:18:47 +00:00
Philipp Schuster
b26488b1bf test_infra: bound SSH session runtime in wait_for_ssh
Allow one-shot SSH commands to install a libssh2 session timeout and
use that path from wait_for_ssh.

This keeps SSH readiness probes from blocking far beyond their caller
provided timeout when the guest network is slow or broken.

On-behalf-of: SAP philipp.schuster@sap.com

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 09:18:47 +00:00
Philipp Schuster
a3b4687caa test_infra: split SSH command helpers by retry behavior
Split the SSH helpers into a one-shot execution path and a retrying
wrapper with linear backoff.

This makes it possible to use a single bounded SSH attempt when tests
need a direct readiness probe while preserving the existing retrying
behavior for callers that expect it.

On-behalf-of: SAP philipp.schuster@sap.com

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 09:18:47 +00:00
Philipp Schuster
c77094bd54 test_infra: add polling helpers for integration tests
Add generic polling helpers for integration tests and build the
SSH wait helpers on top of them.

This lets follow-up test changes replace fixed sleeps with
condition-based waits without duplicating retry logic at each call
site.

On-behalf-of: SAP philipp.schuster@sap.com

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-14 09:18:47 +00:00
dependabot[bot]
ff32912615 build(deps): bump crate-ci/typos from 1.45.0 to 1.45.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.45.0 to 1.45.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.45.0...v1.45.1)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.45.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 00:57:48 +00:00
CMGS
0a4be0c1c7 vmm: extend last MMIO64 allocator to cover full range
The MMIO64 allocator size is computed with alignment truncation:
  size = (range / alignment) * alignment
This loses up to one alignment unit (4 GiB) at the top of the
address space. When a guest (Windows with virtio-win 0.1.285)
programs a BAR near the top of the physical address space, the
allocation fails because the address falls in the truncated gap.

Give the last PCI segment allocator all remaining space up to
the end of the device area, so no addresses are lost.

The `end` parameter of create_mmio_allocators() is an inclusive
address (the last valid byte). Fix the 32-bit caller and tests
to pass inclusive values, consistent with the 64-bit caller
which already uses the inclusive end_of_device_area().

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-04-13 10:25:18 +00:00
CMGS
e38c5c4340 pci: rollback BAR address on failed move_bar
When BAR reprogramming is detected, detect_bar_reprogramming()
eagerly updates the BAR address in config space before the actual
MMIO remapping occurs. If the subsequent move_bar() fails (e.g.
the new address falls outside the allocator range), the config
register retains the new address while the MMIO bus still uses
the old one, leaving the device broken.

Add restore_bar_addr() to undo the config space update when
move_bar() fails, so the device remains functional at its
original address.

For 64-bit BARs, restore both the low and high BAR slots as well
as the corresponding config registers, mirroring the two-slot
update logic in detect_bar_reprogramming().

Implement restore_bar_addr() for all PciDevice implementations
(VirtioPciDevice, VfioPciDevice, VfioUserPciDevice, IvshmemDevice,
PvPanicDevice, and PvmemcontrolPciDevice) by delegating to their
respective PciConfiguration::restore_bar_addr().

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-04-13 10:25:18 +00:00
Anirudh Rayabharam
fd2d33e8ab performance-metrics: Add --continue-on-failure flag and status tracking
Add a --continue-on-failure CLI flag that allows the test harness to
continue executing remaining tests after encountering a failure, instead
of aborting immediately. When set, failed tests are recorded with zeroed
metrics and a "FAILED" status, the report file is always generated, and
the process exits with a non-zero code if any test failed.

Without the flag, the existing fail-fast behavior is preserved.

Also add a "status" field ("PASSED"/"FAILED") to PerformanceTestResult
so report consumers can distinguish successful tests from failed ones.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-04-13 09:53:26 +00:00
Philipp Schuster
2515b06f19 vmm: incoming migration: log duration of state receive and VM resume
Instrument the two main downtime-phase operations on the destination
side - receiving state and resuming the VM - so their costs are visible
in logs and can be iterated on.

The new log messages may look like this:

```text
cloud-hypervisor:   7.283424s: <vmm> DEBUG:vmm/src/lib.rs:948 -- Migration (incoming): recv_snapshot:3ms restore:10ms
cloud-hypervisor:   7.284824s: <vmm> DEBUG:vmm/src/lib.rs:967 -- Migration (incoming): resume:1ms
cloud-hypervisor:   7.284842s: <vmm> DEBUG:vmm/src/lib.rs:977 -- Migration (incoming): Receiving final state and resuming the VM took 15ms
```

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-13 09:52:23 +00:00
Philipp Schuster
f15999823d vmm: outgoing migration: log effective downtime
Use OngoingMigrationContext to measure and log the effective VM downtime
(pause to remote resume) and the cost of each non-trivial step in the
downtime window: snapshotting, sending the snapshot, and awaiting
completion. This makes it straightforward to identify and reduce
downtime as live migration matures.

Example:

```
cloud-hypervisor:   7.703402s: <vmm> INFO:vmm/src/lib.rs:1494 -- Migration completed after 2.2s with a downtime of 298ms (goal was 300ms)
cloud-hypervisor:   7.703453s: <vmm> DEBUG:vmm/src/lib.rs:1500 -- Downtime breakdown: 298ms (final_iter:269ms state:7ms send_state:19ms complete:1ms)
```

Note: downtime is measured on the source only; cross-host clock skew
may cause unreliable results.

# Terminology

At first glance, the use of "state" and "[VM] snapshot" may seem
confusing. As discussed in [0], we use "state" consistently in the
migration code. On the VM side, "snapshotting" is merely the mechanism
used to obtain the VM state.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7979#discussion_r3061359899

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-13 09:52:23 +00:00
Philipp Schuster
f32506447f vmm: add helper to measure successful operation duration
Add a small helper that returns both the successful result of an
operation and the time it took to complete.

Subsequent migration instrumentation uses this to keep timing code
compact and consistent.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-13 09:52:23 +00:00
Philipp Schuster
6a3024c13d vm-migration: add MemoryMigrationContext::empty_finalized() helper
This is helpful in the following to properly aggregate statistics for
local migrations.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-13 09:52:23 +00:00
Philipp Schuster
94f78e5a96 vm-migration: add migration-level context for downtime tracking
Add migration-level context types that extend the existing memory-only
metrics with overall migration duration and downtime breakdown.

OngoingMigrationContext models the sender-side migration progress until
all inputs needed for final downtime accounting are available.
CompletedMigrationContext then stores the finalized migration metrics,
including the final memory iteration, snapshotting, snapshot transfer,
and completion phase.

This provides the data needed to log effective downtime in the VMM and
lays the groundwork for future migration statistics reporting.

# Terminology

At first glance, the use of "state" and "[VM] snapshot" may seem
confusing. As discussed in [0], we use "state" consistently in the
migration code. On the VM side, "snapshotting" is merely the mechanism
used to obtain the VM state.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7979#discussion_r3061359899

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-13 09:52:23 +00:00
Philipp Schuster
f21184c325 vm-migration: expose memory timing needed by migration metrics
Expose the finalized per-iteration timing fields needed by higher-level
migration metrics and factor the iteration-overhead calculation into a
small helper.

This keeps the existing MemoryMigrationContext behavior intact while
making the timing data easier to consume from migration-level context
in the following commits.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-13 09:52:23 +00:00
Philipp Schuster
b9c3cfb14d vm-migration: context: move unit tests into sub module
This helps to better separate the unit tests from the new ones in the
following commit.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-04-13 09:52:23 +00:00
Max Makarov
c99ed77d1a tests: pci: verify per-segment ACPI _UID in DSDT
Extend test_pci_multiple_segments_numa_node to assert that every
PNP0A08 host bridge in the guest DSDT exposes a unique _UID
matching its PCI segment id. Linux surfaces the evaluated _UID
value through /sys/bus/acpi/devices/PNP0A08:*/uid, so the check
is a single additional ssh command on top of the existing test
plumbing.

This test is used (rather than test_pci_multiple_segments) so
that the assertion runs on both x86_64 and aarch64: the numa_node
variant boots through edk2 firmware on aarch64, making ACPI (and
PNP0A08 host bridges) available, whereas the non-firmware variant
uses FDT on aarch64 and exposes no PNP0A08 nodes.

Without a per-segment _UID, two PNP0A08 nodes share _UID=0 which
violates ACPI 6.5 section 6.1.12 and triggers BSOD 0xA5 on
Windows guests. This assertion would catch any future regression
of that kind.

Signed-off-by: Max Makarov <maxpain@linux.com>
2026-04-13 09:39:55 +00:00
Max Makarov
87ba83bb01 vmm: pci_segment: use segment id as ACPI _UID
The ACPI specification requires _UID to be unique across devices
sharing the same _HID (ACPI 6.5 section 6.1.12). Currently every
PciSegment emits _UID=0 for its PNP0A08 host bridge, which violates
the spec when num_pci_segments > 1.

Windows guests detect this during ACPI namespace enumeration and
abort boot with BSOD 0xA5 ACPI_BIOS_ERROR, pointing at the _UID
object of the second PNP0A08 node. Linux guests are lenient and
silently accept the collision, so the issue has gone unnoticed.

Use self.id as _UID, matching what _SEG does on the line above.
For single-segment VMs (id == 0) this is a no-op at runtime.

Signed-off-by: Max Makarov <maxpain@linux.com>
2026-04-13 09:39:55 +00:00
Bo Chen
a3899a9783 tests: Add retries for windows integration tests
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-13 07:40:02 +00:00
Bo Chen
e48b14dcac scripts: Add retries for vfio integration tests
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-13 07:40:02 +00:00
Bo Chen
e8b6fe0548 tests: vfio: Capture guest dmesg when nvidia-smi failed
The guest dmesg can provide more context from the guest kernel, say
Nvidia driver errors, IOMMU faults, etc.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-13 07:40:02 +00:00
Bo Chen
6adc4f2c90 tests: vfio: Add more checks on the Nvidia GPU from the guest
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-13 07:40:02 +00:00
Demi Marie Obenour
8b212aafc0 ci: Escape $ in heredoc in MSHV workflow script
This is a preexisting bug in the MSHV integration tests,
but previously it only caused a warning.  With commit
Fixes: 5b67b8994a ("ci: Use set -eufo pipefail") it becomes an error.

Fixes: 5b67b8994a ("ci: Use set -eufo pipefail")
Fixes: #7996
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-04-13 06:44:09 +00:00
Saravanan D
23a980cd54 pci: expand sub-page VFIO BAR mmap to page size
On aarch64 with 64K host pages, VFIO passthrough of devices with
sub-page BARs (e.g. 16K NVMe BAR0) crashes with EINVAL from
KVM_SET_USER_MEMORY_REGION, which requires memory_size to be a
multiple of the host page size.

Expand the mmap to page size instead of rejecting it, matching
QEMU's approach. The kernel's vfio_pci_probe_mmaps() already
verifies that sub-page BARs are page-aligned and reserves the
remainder of the page, so expansion is safe at offset 0. Reject
sub-page sparse areas at non-zero offsets where this guarantee
does not apply.

The expanded mmap region will not overlap with the relocated MSI-X
trap region because fixup_msix_region() ensures MSI-X relocation
at >= page_size offset.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-04-12 11:12:48 +00:00
Saravanan D
2ddbc5abbb scripts: use arch OVMF downloads and bump fw tag
Rename download_ovmf to download_amd64_ovmf and add a separate
download_aarch64_ovmf for CLOUDHV_EFI.fd

Replace build_edk2 with download_aarch64_ovmf in aarch64 scripts

Update OVMF firmware tag to ch-1e1b96f126

Update CLOUDHV.fd sha1sum in sha1sums-x86_64

Add CLOUDHV_EFI.fd sha1sum in sha1sums-aarch64-common

Fixes: #7622

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-04-12 10:10:09 +00:00
Changyuan Lyu
490e338e16 pci: synchronize VfioMsix::cap and VfioMsix::bar
Currently, when snapshoting a running VFIO device with MSI-X enabled,
we get a snapshot where `msix_config.state.enabled` is not consistent
with `msix_state.cap.msg_ctl`,

```jsonc
{
  "snapshots": {
    "vfio_common": {
      "snapshots": {
        "msix_config": {
          "snapshots": {},
          "state": {
            "enabled": true
            // ...
          }
        },
        // ..
      },
      "state": {
        "msix_state": {
          "cap": {
            "msg_ctl": 3,
            "table": 1,
            "pba": 2049
          },
          // ...
        }
        // ...
      }
    }
  },
  // ...
}
```

The root cause is, after a `MsixCap` is parsed from the device PCI
config space and propagated to a corresponding `MsixConfig`,
`MsixCap::msg_ctl` is never get updated at runtime, only
`MsixConfig::msg_ctl` is updated.

This commit makes `VfioMsix::update` update both `VfioMsix::bar` (of
type `MsixConfig`) and `VfioMsix::cap` (of type `MsixCap`).

Signed-off-by: Changyuan Lyu <changyuanl@google.com>
2026-04-11 09:21:50 +00:00
JP Kobryn
bdc7a6947d vmm: add per-zone mergeable option to --memory-zone
Add a `mergeable` field to `MemoryZoneConfig` so that KSM page merging
can be enabled selectively per memory zone rather than globally for all
guest RAM.

Previously, `MADV_MERGEABLE` was only controllable via the top-level
`--memory mergeable=on` flag, which applied uniformly to all regions.
With this change, users can leave boot memory unmerged while enabling
KSM only on hotplug zones:

  --memory size=0,hotplug_method=virtio-mem
  --memory-zone id=boot,size=512M,shared=on,mergeable=off
  --memory-zone id=hotplug,size=256M,hotplug_size=1G,shared=off,mergeable=on

The `MemoryZone` runtime struct now carries the `mergeable` flag so
that both `allocate_address_space` and `add_ram_region` can apply
per-zone `MADV_MERGEABLE` instead of the global `self.mergeable`.
The top-level `--memory mergeable=on` path continues to work unchanged:
the default zone is synthesised from `MemoryConfig` and inherits its
`mergeable` value.

AI/LLM disclosure: this patch was co-authored with
GitHub Copilot and Claude Code (Opus 4.6).

Signed-off-by: JP Kobryn <inwardvessel@gmail.com>
2026-04-11 09:01:57 +00:00
Anirudh Rayabharam
3a23e2f841 ci: fix jq usage in mshv-infra.yaml
Workflow runs fail in the "Get Location" step with:

jq: error (at <stdin>:9): string ("100") and number (0) cannot be added

Use tonumber to explicitly convert string to number instead of the "+ 0"
trick.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-04-11 10:03:20 +01:00
Demi Marie Obenour
b8a61da06a ci: Use jq instead of bash arithmetic
jq's arithmetic is much more robust.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-04-11 00:47:04 +00:00
Demi Marie Obenour
dcdf16b8ff ci: Use bash regex instead of sed
Easier to read and more reliable.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-04-11 00:47:04 +00:00
Demi Marie Obenour
295a76ea28 ci: Use a variable of type number for the OS disk size
It's better to let GitHub Actions validate this.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-04-11 00:47:04 +00:00
Demi Marie Obenour
5b67b8994a ci: Use set -eufo pipefail
Most scripts can use it and it is good at catching errors.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-04-11 00:47:04 +00:00
Demi Marie Obenour
23e139c0f8 ci: Double-quote variables in GitHub Actions
This is best practice for shell scripts.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-04-11 00:47:04 +00:00
Bo Chen
9d47769bc2 tests: Add iommufd integration tests
Add an `iommufd` flag to existing VFIO integration tests. When false,
tests use the legacy vfio container/group backend (existing behavior).
When true, tests use vfio cdev with iommufd and vfio_p2p_dma=off.

vfio_p2p_dma=off is required because the VFIO test runner uses a stock
Ubuntu 24.04 kernel (v6.8) which does not support mapping device MMIO
pages (VM_PFNMAP) through iommufd, causing IOMMU_IOAS_MAP to fail with
-EFAULT on MMIO BAR regions.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-10 22:55:38 +00:00
Bo Chen
87992c77c1 vmm: Introduce option --platform vfio_p2p_dma=on|off
Add a user-configurable option to control whether VFIO device MMIO BAR
regions are DMA-mapped into the host IOMMU address space.

This mapping is required for peer-to-peer DMA between devices (e.g.
NVLink, RDMA NIC accessing GPU VRAM). However, iommufd on upstream
kernels does not support mapping device MMIO pages (VM_PFNMAP), causing
IOMMU_IOAS_MAP to fail with -EFAULT. Kernels with the NVIDIA PFNMAP
workaround or future kernels with DMABUF-based mapping
(IOMMU_IOAS_MAP_FILE) handle this correctly.

The option defaults to `on` to preserve existing behavior. Users on
vanilla kernels using iommufd should set `vfio_p2p_dma=off` to skip
MMIO BAR DMA mapping.

A validation check ensures that `x_nv_gpudirect_clique` (which depends
on P2P DMA) cannot be used when `vfio_p2p_dma=off`.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-10 22:55:38 +00:00
Bo Chen
32c459c3dc virtio-devices, vmm: Add seccomp rules for iommufd and vfio cdev
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-10 22:55:38 +00:00
Bo Chen
fe5f991c37 vmm: Support device passthrough with vfio cdev and iommufd
When `--platform iommufd=on` is set, use the vfio cdev interface backed
by iommufd instead of the legacy vfio container/group interface for
device passthrough.

The cdev path opens '/dev/iommu' via IommuFd, allocates an IOAS, and
binds VFIO devices through VfioIommufd. The legacy container/group path
remains the default and is used when iommufd is not enabled.

Add iommufd-ioctls as a workspace dependency and enable the "vfio_cdev"
feature on vfio-ioctls for KVM builds.

Fixes: #6892

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-10 22:55:38 +00:00
Bo Chen
13972a0edf vmm: Introduce option --platform iommufd=on|off
This option allows user to configure VFIO device pass-through with
iommufd (e.g. vfio cdev mode) or not (e.g. vfio legacy mode).

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-10 22:55:38 +00:00
Bo Chen
7f377eadd9 vmm: Fix --platform syntax with optional feature flags
The `--platform` help string was hardcoded and did not reflect which
optional features (tdx, sev_snp) were actually enabled in. Build the
syntax string dynamically as `PlatformConfig::syntax()`, conditionally
appending feature-gated options so the CLI help stays accurate.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-10 22:55:38 +00:00
dependabot[bot]
de601c5eaa build(deps): bump the non-rust-vmm group across 2 directories with 10 updates
Bumps the non-rust-vmm group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [libc](https://github.com/rust-lang/libc) | `0.2.183` | `0.2.184` |
| [signal-hook](https://github.com/vorner/signal-hook) | `0.4.3` | `0.4.4` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.9.0` | `1.9.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.58` | `1.2.59` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.13.0` | `2.13.1` |
| [libz-sys](https://github.com/rust-lang/libz-sys) | `1.1.25` | `1.1.28` |
| [semver](https://github.com/dtolnay/semver) | `1.0.27` | `1.0.28` |
| [toml_datetime](https://github.com/toml-rs/toml) | `1.1.0+spec-1.1.0` | `1.1.1+spec-1.1.0` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.25.8+spec-1.1.0` | `0.25.10+spec-1.1.0` |

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

| Package | From | To |
| --- | --- | --- |
| [libc](https://github.com/rust-lang/libc) | `0.2.183` | `0.2.184` |
| [signal-hook](https://github.com/vorner/signal-hook) | `0.4.3` | `0.4.4` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.9.0` | `1.9.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.58` | `1.2.59` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.13.0` | `2.13.1` |
| [semver](https://github.com/dtolnay/semver) | `1.0.27` | `1.0.28` |
| [toml_datetime](https://github.com/toml-rs/toml) | `1.1.0+spec-1.1.0` | `1.1.1+spec-1.1.0` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.25.8+spec-1.1.0` | `0.25.10+spec-1.1.0` |



Updates `libc` from 0.2.183 to 0.2.184
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.184/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.183...0.2.184)

Updates `signal-hook` from 0.4.3 to 0.4.4
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.4.3...v0.4.4)

Updates `arc-swap` from 1.9.0 to 1.9.1
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.9.0...v1.9.1)

Updates `cc` from 1.2.58 to 1.2.59
- [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.58...cc-v1.2.59)

Updates `indexmap` from 2.13.0 to 2.13.1
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.13.0...2.13.1)

Updates `libz-sys` from 1.1.25 to 1.1.28
- [Release notes](https://github.com/rust-lang/libz-sys/releases)
- [Commits](https://github.com/rust-lang/libz-sys/compare/1.1.25...1.1.28)

Updates `semver` from 1.0.27 to 1.0.28
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.27...1.0.28)

Updates `toml_datetime` from 1.1.0+spec-1.1.0 to 1.1.1+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v1.1.0...toml_datetime-v1.1.1)

Updates `toml_edit` from 0.25.8+spec-1.1.0 to 0.25.10+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.25.8...v0.25.10)

Updates `toml_parser` from 1.1.0+spec-1.1.0 to 1.1.2+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.1.0...toml_parser-v1.1.2)

Updates `libc` from 0.2.183 to 0.2.184
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.184/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.183...0.2.184)

Updates `signal-hook` from 0.4.3 to 0.4.4
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.4.3...v0.4.4)

Updates `arc-swap` from 1.9.0 to 1.9.1
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.9.0...v1.9.1)

Updates `cc` from 1.2.58 to 1.2.59
- [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.58...cc-v1.2.59)

Updates `indexmap` from 2.13.0 to 2.13.1
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.13.0...2.13.1)

Updates `semver` from 1.0.27 to 1.0.28
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.27...1.0.28)

Updates `toml_datetime` from 1.1.0+spec-1.1.0 to 1.1.1+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v1.1.0...toml_datetime-v1.1.1)

Updates `toml_edit` from 0.25.8+spec-1.1.0 to 0.25.10+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.25.8...v0.25.10)

Updates `toml_parser` from 1.1.0+spec-1.1.0 to 1.1.2+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.1.0...toml_parser-v1.1.2)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.184
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: signal-hook
  dependency-version: 0.4.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.9.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.59
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: indexmap
  dependency-version: 2.13.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libz-sys
  dependency-version: 1.1.28
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: semver
  dependency-version: 1.0.28
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_datetime
  dependency-version: 1.1.1+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.25.10+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_parser
  dependency-version: 1.1.2+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.184
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: signal-hook
  dependency-version: 0.4.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.9.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.59
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: indexmap
  dependency-version: 2.13.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: semver
  dependency-version: 1.0.28
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_datetime
  dependency-version: 1.1.1+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.25.10+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_parser
  dependency-version: 1.1.2+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 18:12:11 +00:00
JP Kobryn
474106a067 vmm: skip uefi allocation on direct boot
A 4M uefi_region is allocated unconditionally. When directly booting a
kernel, it goes unused. Avoid the allocation in this case by moving the
call to add_uefi_flash() to load_firmware().

Also extended add_uefi_flash() to riscv64 since it shares the
load_firmware() path. It looked like up to this point a firmware boot on
riscv64 would panic with an uninitialized uefi_flash.

Signed-off-by: JP Kobryn <inwardvessel@gmail.com>
2026-04-09 23:18:00 +00:00
Thomas Prescher
84c5e48bdb scripts: use latest ovmf version for x86_64
This commit bumps ovmf to ch-13b4963ec4 [1].

[1] https://github.com/cloud-hypervisor/edk2/releases/tag/ch-13b4963ec4

On-behalf-of: SAP thomas.prescher@sap.com
Signed-off-by: Thomas Prescher <thomas.prescher@cyberus-technology.de>
2026-04-09 17:23:02 +00:00
Anirudh Rayabharam
fa949678d1 scripts: build mshv feature too for dbus, fw_cfg & ivshmem tests
The aarch64 dbus, fw_cfg & ivshmem tests don't build the mshv feature
causing them to fail when run on MSHV. Fix by building the mshv feature
too just like the x86 version of the script does.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-04-07 19:12:48 +00:00
Jared White
77ce3f6cbf vmm: memory_actual_size reflects hotplug state
It is desirable to be able to track the progress of memory hotplug.
Update the memory_actual_size field to query the current plugged size
from virtio-mem to enable this.

Signed-off-by: Jared White <git@jaredwhite.dev>
2026-04-07 01:37:37 +00:00
Max Makarov
aef0a43b52 vdpa: fix RX failure after device reset by always using base 0
After a vDPA device reset, activate_vdpa() read avail_idx from guest
memory to pass as the vring base via VHOST_SET_VRING_BASE. However,
the guest memory still contained the stale avail_idx from the previous
session. For a 256-entry ring, this meant base=256, causing the
hardware to believe the entire RX ring was consumed with no available
buffers — RX silently stopped while TX continued to work.

QEMU handles this correctly by tracking last_avail_idx internally
(reset to 0 in virtio_reset()) and passing that value, rather than
reading from guest memory.

Fix by always passing base=0 to set_vring_base(). After a device
reset, both the guest driver and the vhost backend restart their rings
from index 0. For live migration, the correct base should come from
VHOST_GET_VRING_BASE (saved before the migration), not guest memory.

Tested with mlx5_vdpa (ConnectX-6 Dx) + Windows Server 2025 (netkvm).
Before: RX=0 after 3rd driver activation. After: full connectivity.

Signed-off-by: Max Makarov <maxpain@linux.com>
2026-04-07 00:48:19 +00:00
Peter Oskolkov
f56c8392ea virtio-devices: vsock: RST vsocks on snapshot restore
Otherwise guest connections just hang.

Signed-off-by: Peter Oskolkov <posk@google.com>
2026-04-07 00:36:57 +00:00
Leander Kohler
f7f9895d57 hypervisor: kvm: preserve guest MTRR MSRs
KVM_GET_MSR_INDEX_LIST does not consistently include all
guest-programmable MTRR MSRs.

During save/restore while booting, the VMM initially sets only
MSR_MTRRdefType, then guest firmware or other early boot code can
program additional MTRR state before the snapshot is taken. If those
MSRs are missing from the vCPU MSR buffer, snapshot omits part of the
guest's MTRR configuration and restore resumes with an incomplete
MTRR map.

Add the guest-programmable MTRR MSRs to the KVM MSR index list used
to build the vCPU MSR buffer so the existing snapshot/restore path
preserves the guest's MTRR state.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-04-07 00:33:40 +00:00
Muminul Islam
676c0d320b build: use latest 0.6.8 mshv crates
Use mshv-{ioctls, bindings) with the latest versions
that fixes a bug on interrupt vector.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-03 22:24:12 +00:00
Damian Barabonkov
8248650e79 pci: Handle dword MSI-X control writes
Some guests update the MSI-X capability through a 32-bit write at
offset 0 instead of a 16-bit write at offset 2. Update the cached
Message Control state for that path as well so MSI-X enablement stays
in sync with the guest configuration.

Add a short comment documenting why the dword write path also updates
the cached MSI-X Message Control state.

This is important for passthrough GPUs, where MSI-X interrupts are used
during NVIDIA Fabric Manager registration. Without updating the cached
state on the dword write path, interrupt delivery can remain stale and
GPU initialization or fabric registration can fail.

Signed-off-by: Damian Barabonkov <dbctl@pm.me>
2026-04-02 23:57:31 +00:00
Sebastian Eydam
17919a7a8c tests: add integration test for migration with multiple TCP connections
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
ecddc6f842 vmm: add upper limit for amount of parallel connections during migration
Check that the amount of parallel connections does not exceed 128 and
update documentation.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
a9a832f392 vmm: validate VmSendMigrationData
Validates that there are no conflicting options set, and that the
destination URL is valid.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
fb19881918 vm-migration: add connections field to API
And wire everything up. From now on the multiple connections feature can
be used.

This commit series is heavily based on Julian Stecklina's work, so kudos
to him!

Co-authored-by: Julian Stecklina <julian.stecklina@cyberus-technology.de>
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
55e6971c47 vmm: funnel VM memory via additional connections abstraction
At this point, we are still only using a single connection.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
07484abd35 vmm: implement functionality to send via multiple connections
Implements the functionality to send VM memory via multiple connections
during a live migration.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
5a2dea8fa6 vmm: implement a gate
This gate behaves like a barrier, but it can be opened, meaning that
threads can be released before all threads arrived at the gate. This
lets us release waiting threads in case of an error, which will be
important for the sender side of a live migration with multiple
TCP connections.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
5e563e7ea3 vm-migration: allow partitioning memory tables
For sending memory over multiple connections, we need a way to split up
the work. With these changes, we can chop a memory table into same-sized
chunks for transmit

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
5c556880dd vmm: implement functionality to accept multiple connections
Adds the functionality to accept multiple connections on the receiver
side of a live migration. A thread listens for incoming connections and
creates a worker for each new connection.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
058954a8c1 vmm: make receive_memory_ranges take the requests directly
This just removes some unnecessary indirections.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
98ece1e347 vmm: add functionality for an abortable accept for sockets
With this, the receiver side of a migration can wait for incoming
connections, while also being able to abort the accept when the
migration is done.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
7311211b38 vmm: allow keeping the socket listener around
This allows accepting multiple connections in the migration receive
path.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
765311085f vmm: keep direct reference to guest memory around
That way we avoid having to grab a lock when receiving a chunk of memory
over the migration socket. This is a necessary prerequisite for having
multiple memory receiving threads.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
ec42ee8004 vmm: extract receive_memory_regions from memory manager
The memory manager is guarded by a mutex, thus parallel accesses to it
and its members are not possible. But we have to execute this function
in parallel when we introduce multiple TCP connections. Otherwise, the
workers who receive the data and write it into guest memory will block
on each other, and thus slow down the migration.

Also rename the function to receive_memory_ranges for better naming
consistency.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
e175ad64f2 vmm: move SocketStream into the migration_transport module
This is mainly to clean up the lib.rs a bit more.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
9248143e18 vmm: extract send_memory_regions from vm
And rename it for better naming consistency.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
e03c0f7708 vmm: move function to send dirty pages into transport module
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
693cdccbb0 vmm: extract a helper to send the VM state
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
196e48af30 vmm: extract a helper to send the VM config
This further decreases boilerplate code in lib.rs while keeping the
behavior.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
d4a8d55074 vmm: extract small request/response helpers to reduce boilerplate
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
bb3e1b4073 vmm: stop removing the UNIX socket file
When doing a local migration using a UNIX socket, we removed the UNIX
socket file after accepting the connection. The VMM does not own this
socket file, which makes this an unsafe operation. Thus, we stop doing
that.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Sebastian Eydam
d2f6476149 vmm: move migration socket helpers into transport module
On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-04-02 23:43:42 +00:00
Muminul Islam
1b479e40ea performance-metrics: Remove duplicate remote_command
Remove the local remote_command() function from
performance_tests.rs. The identical function is now
available from test_infra via the existing glob import.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 14:39:21 +00:00
Muminul Islam
c8bfac66f4 test_infra: Move remote_command to test_infra
Move remote_command() and remote_command_w_output() from
tests/common/utils.rs into test_infra/src/lib.rs to allow
reuse across crates.

The cloud-hypervisor integration tests already use
'use test_infra::*', so the functions are available
without any caller changes.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 14:39:21 +00:00
Muminul Islam
1400e614cb performance-metrics: Remove duplicate x86_64 FOCAL_IMAGE_NAME
Remove the local x86_64 FOCAL_IMAGE_NAME constant from
performance_tests.rs. The identical public constant from
test_infra is already available via wildcard import.

The aarch64 definition is kept as it differs from test_infra:
performance-metrics uses a specific image with the
'-update-tool' suffix.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 14:39:21 +00:00
Muminul Islam
4cf10737e4 performance-metrics: Remove duplicate DIRECT_KERNEL_BOOT_CMDLINE
Remove the local DIRECT_KERNEL_BOOT_CMDLINE constant from
performance_tests.rs. The identical public constant from
test_infra is already available via wildcard import.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 14:39:21 +00:00
Muminul Islam
488927a5ea performance-metrics: Remove duplicate direct_kernel_boot_path()
Remove the local direct_kernel_boot_path() function and unused
PathBuf import from performance_tests.rs. The identical public
function from test_infra is already available via wildcard
import.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 14:39:21 +00:00
Anatol Belski
5ae329305a virtio-devices: block: Fix writeback mode update flow
Virtio v1.2 says that if CONFIG_WCE is negotiated
but FLUSH is not, the device must initialize writeback to 0.
It also says that if CONFIG_WCE was not negotiated but FLUSH
was, the driver should assume presence of a writeback cache.

Introduce a pure is_writeback_enabled helper and a
set_writeback_mode helper. This makes the two call flows
explicit:

* write_config resolves the guest requested mode against the
  negotiated features before storing it back
* activate starts from the default writeback preference and then
  resolves it against the negotiated features
* reset restores the initial writeback state

This keeps the config space value and the runtime writeback flag
in sync and makes the spec driven fallback easier to follow.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-02 13:04:17 +00:00
Chinmoy
d0b253472d pci, devices, virtio-devices, vmm: Refactor allocate_bars
Refactor PciDevice::allocate_bars trait and all implementations
to take &mut SystemAllocator instead of &Arc<Mutex<SystemAllocator>>,
removing double indirection.

The caller in device_manager.rs now acquires the lock before
calling allocate_bars.

Signed-off-by: Chinmoy <daschinmoyy21@gmail.com>
2026-04-02 11:52:04 +00:00
Chinmoy
ef9133a3ee vmm: acpi: Take &T instead of &Arc<Mutex<T>>
Refactor ACPI table creation functions to accept borrowed
references, removing double indirection and moving locking
to callers.

Signed-off-by: Chinmoy <daschinmoyy21@gmail.com>
2026-04-02 11:52:04 +00:00
CMGS
7461143194 net_util: fix ctrl_queue used_len to only count written bytes
The control queue handler passed the total length of all
descriptors (header + data + status) as used_len to add_used.
Per virtio spec section 2.6.8, used_len must only count bytes
written to device-writable descriptors. The device only writes
the 1-byte status/ack field.

Windows NetKVM >= 0.1.285 strictly checks this value and calls
NdisMRemoveMiniport when len != sizeof(virtio_net_ctrl_ack),
removing the network adapter immediately after activation.

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-04-02 11:16:58 +00:00
Wei Liu
c60168256a net_util: Tolerate some unsupported command classes
Windows NetKVM driver (>= 0.1.271) issues unsupported command classes
even when they are not even advertised.

According to the Virtio 1.2 specification:

RX, VLAN, and ANNOUNCE control paths are only meaningful when their
corresponding features are negotiated in sections 5.1.3.1, 5.1.6.5.1.2,
5.1.6.5.2.2, and 5.1.6.5.4.1.

RX and VLAN are explicitly described as best-effort in sections
5.1.6.5.1 and 5.1.6.5.3.

Instead of returning an error to the guest, return success to the guest.

Fixes: #7925
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-04-02 11:16:58 +00:00
dependabot[bot]
8026eb177f build(deps): bump crate-ci/typos from 1.44.0 to 1.45.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.44.0 to 1.45.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.44.0...v1.45.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-02 05:19:25 +00:00
Muminul Islam
f6b4061629 block: Return BlockResult from RawFileAsyncAio::new()
Change the return type of RawFileAsyncAio::new() from
std::io::Result<Self> to BlockResult<Self>, wrapping
internal errors from EventFd::new() and IoContext::new()
in BlockError with DiskFileError::NewAsyncIo.

This simplifies the caller in AsyncDiskFile::new_async_io()
which no longer needs its own error mapping.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
783cc8bbd9 block: Remove legacy DiskFile impl from RawFileDiskAio
Remove the old async_io::DiskFile trait implementation from
RawFileDiskAio, now that the new disk_file trait hierarchy
is fully implemented.

Clean up unused imports: DiskFile and DiskFileResult from
crate::async_io.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
6df7cda4ad block: Implement AsyncDiskFile trait for RawFileDiskAio
Add disk_file::AsyncDiskFile trait implementation for
RawFileDiskAio with try_clone() and new_async_io() methods.

try_clone() duplicates the underlying file descriptor and
wraps it in a new RawFileDiskAio. new_async_io() creates a
RawFileAsyncAio (Linux AIO) backend, wrapping errors in
BlockError instead of DiskFileError.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
39bbbaaa59 block: Implement DiskFile marker trait for RawFileDiskAio
Add empty disk_file::DiskFile impl for RawFileDiskAio.
This marker supertrait requires DiskSize + Geometry + Sync,
all of which are now satisfied.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
62264cb3c7 block: Implement Resizable trait for RawFileDiskAio
Add disk_file::Resizable trait implementation for
RawFileDiskAio. Calls file.set_len(size) and wraps the
I/O error in BlockError on failure.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
b7cf8737ac block: Implement SparseCapable trait for RawFileDiskAio
Add disk_file::SparseCapable trait implementation for
RawFileDiskAio. Delegates to probe_sparse_support() to
detect whether the underlying file supports hole-punching.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
190380c9ba block: Implement Geometry trait for RawFileDiskAio
Add disk_file::Geometry trait implementation for
RawFileDiskAio. Probes disk topology from the file,
falling back to defaults on failure. Takes &self instead
of &mut self and uses unwrap_or_else for cleaner error
handling.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
df890599c2 block: Implement DiskFd trait for RawFileDiskAio
Add disk_file::DiskFd trait implementation for
RawFileDiskAio. Delegates to file.as_raw_fd() via
BorrowedDiskFd, taking &self instead of &mut self.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
78bbdbef86 block: Implement PhysicalSize trait for RawFileDiskAio
Add disk_file::PhysicalSize trait implementation for
RawFileDiskAio. Returns the physical size from
query_device_size wrapped in BlockError on failure,
consistent with the DiskSize impl.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
fca54cb142 block: Implement DiskSize trait for RawFileDiskAio
Add disk_file::DiskSize trait implementation for
RawFileDiskAio using BlockError and BlockResult. Takes
&self instead of &mut self.

Add BlockError, BlockErrorKind, BlockResult, and disk_file
imports needed by this and subsequent trait impls.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Muminul Islam
98dbe6d128 block: Derive Debug on RawFileDiskAio
Add #[derive(Debug)] to RawFileDiskAio. This is required
by the new disk_file traits which have Send + Debug bounds.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-04-02 01:18:30 +00:00
Anatol Belski
9db5f0439e tests: Stabilize block rate limiter workloads
Add a ramp time before measuring the block rate limiter tests so
both the single device and group workloads are measured after
warm up to make the measurements less sensitive to startup
transients.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-01 13:36:58 +00:00
Anatol Belski
36d6dacee2 tests: Increase group block rate limiter refill window
Increase the group block refill time from 100 ms to 1000 ms and
scale the shared bucket sizes to preserve the target rate.

Set the one time burst to 0 to avoid transient overshoot and use
the shared block runtime constant directly in the group path.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-01 13:36:58 +00:00
Anatol Belski
f116038111 tests: Increase rate limiter refill time
The rate limiter token bucket has a fixed 100 ms cool down time
that pauses I/O whenever the bucket empties. With a 100 ms refill
time, the actual throughput drops to roughly half of the target
rate and causes the tests to miss their target.

Increase the net and single block refill time from 100 ms to
1000 ms and scale the bucket sizes by 10x to preserve the target
rate. Set the one time burst to 0 to avoid overshooting the upper
bound, and raise the runtime constants from 10 s to 20 s for
steadier measurements.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-01 13:36:58 +00:00
Anatol Belski
6de3d5279f tests: Constify rate limiter runtimes
Introduce named constants for the net and single block rate limiter
test runtimes and use them directly at the call sites.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-04-01 13:36:58 +00:00
Rob Bradford
a8904a93a6 build: Only run ARM64 CI on merge queue
Unfortunately with a single ARM64 machine this has now become a
bottleneck for landing PRs. Copy the methodology we use for existing
jobs that we only run on the MQ by creating dummy jobs that run on the
GH hosted runner (ubuntu-latest) allowing the PR to transition into
the MQ by passing the required checks.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-01 15:49:33 +01:00
Rob Bradford
a99c40dbbb tests: Add live migration test for virtio-fs
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-01 11:35:38 +00:00
Rob Bradford
fa29dbd0c5 virtio-devices: Reuse common shutdown code in drop implementations
Now that the VhostUserCommon::shutdown implementation has been filled
out to support migration it can also be used for the drop
implementations in the vhost-user devices.

It's worth noting that the call to wait_for_epoll_threads() was a no-op
as those threads are only configured on conventional virtio devices.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-01 11:35:38 +00:00
Rob Bradford
266ad8aa0e virtio-devices: vhost_user: Advertise LOG_ALL feature
Advertising support for this virtio feature is required to enable
support for migration. (Along with the LOG_SHMFD protocol feature.)

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-01 11:35:38 +00:00
Rob Bradford
070f3bbea1 virtio-devices: Reject dirty logging if backend does not support it
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-01 11:35:38 +00:00
Rob Bradford
cc1735c399 virtio-devices: vhost_user: Use the VhostUserHandle enum for LOG_ALL
This is equivalent value but removes the need to manually use the
constant to shift.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-01 11:35:38 +00:00
Rob Bradford
68691db37b virtio-devices: vhost_user: Correctly shutdown epoll thread
If the epoll thread is paused, which would be expected as a part of live
migration/snapshot-restore unpause the thread so that it can receive the
kill event. This mirrors the reset() behaviour of virtio devices. It is
important here so as to close the connection with the vhost-user-backend
to allow same host and --local migration and since after getting the
device state the vhost-user backend should no longer be used.

As a result of this change we can do --local and same-host migration
with virtio-fs.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-01 11:35:38 +00:00
Rob Bradford
63aeb597ef virtio-devices: Move epoll_thread to VhostUserCommon
This is used by all devices so it can be part of the common state.
Moving it simplifies the code and simplifies some future improvements
around shutdown for migration.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-01 11:35:38 +00:00
Rob Bradford
a0bbef3a76 virtio-devices: Embed VirtioCommon in VhostUserCommon
Since vhost-user devices are always virtio devices it makes sense to
structure this struct inside the VhostUserCommon struct. This then also
makes some of the methods on VhostUserCommon cleaner since they can now
act directly on the common virtio bits (e.g. for kill_evt)

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-04-01 11:35:38 +00:00
Leander Kohler
db93c6fdc7 pci: Save deferred BAR reprogramming state
OVMF can reprogram PCI BARs while memory space decoding is disabled.
Cloud Hypervisor defers the corresponding BAR move in
`pending_bar_reprogram` until the PCI command register enables Memory
Space again.

That deferred state was not part of `PciConfigurationState`. A
snapshot taken in that window restored the new BAR values in PCI
config space, but lost the pending BAR relocation needed to update the
VMM-side BAR mapping.

The restore logs show guest MMIO accesses to the reprogrammed BAR
addresses `0xc0000000`, `0x100000000`, and `0x100080000` hitting
unregistered addresses. The firmware serial output shows OVMF
assigning those same BAR addresses during PCI resource allocation,
then reaching BDS, finding the mass-storage device, and failing to
boot from it.

Serialize and restore `pending_bar_reprogram` so deferred BAR moves
survive snapshot and restore.

Co-authored-by: Thomas Prescher <thomas.prescher@cyberus-technology.de>
Co-authored-by: Julian Schindel <julian.schindel@cyberus-technology.de>
On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-04-01 10:20:11 +00:00
Bo Chen
0686045290 vmm: interrupt: Allocate GSIs for MSI/MSI-X interrupt vectors lazily
Previously, GSIs were eagerly allocated for all MSI-X vectors a device
advertises (i.e. the maximum the device can support). This can easily
exhaust KVM_MAX_IRQ_ROUTES (4096) with modern NVMe devices that support
up to 2048 MSI-X vectors.

Defer GSI allocation to the first time an interrupt vector is
unmasked. The EventFd is still created eagerly since external
components (e.g. VFIO) need it at device init time.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-01 01:25:44 +00:00
Bo Chen
820140930a vmm: interrupt: Reduce visibility of internal types and methods
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-04-01 01:25:44 +00:00
Anatol Belski
9f980969fc block: Add UnsupportedFlags error variant for flag validation
Introduce ExecuteError::UnsupportedFlags to carry both the
request type and the rejected flags value, replacing the
generic ExecuteError::Unsupported at discard and write zeroes
flag validation sites. This provides structured context for
debugging without changing the returned VIRTIO_BLK_S_UNSUPP
status.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-31 23:07:26 +00:00
CMGS
e4e2a37afa block: Restrict DISCARD to explicit sparse=true
PR #7852 fixed the missing VirtioBlockConfig fields but did not
change the feature advertisement logic. The condition
`sparse || disk_image.supports_zero_flag()` causes qcow2 to
advertise DISCARD even with sparse=false, because qcow2 can
mark clusters as zero (supports_zero_flag() returns true).

Windows viostor BSODs (DRIVER_IRQL_NOT_LESS_OR_EQUAL) when
DISCARD is advertised on qcow2 backends, making sparse=off
ineffective as a workaround for qcow2 images.

Restrict DISCARD to explicit sparse=true only. WRITE_ZEROES
remains available for all sparse-capable backends.

Fixes #7849

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-03-31 18:57:52 +00:00
Anatol Belski
8ca5210603 block: Reject write zeroes with unknown flags
The virtio spec v1.2 in 5.2.6.2 requires that the device
MUST return VIRTIO_BLK_S_UNSUPP for write zeroes commands
if any unknown flag is set.

Add an early check that rejects requests with reserved flag
bits set by returning VIRTIO_BLK_S_UNSUPP via the existing
ExecuteError::Unsupported variant.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-31 18:54:19 +00:00
Anatol Belski
af1d69a9dd block: Reject discard requests with flags set
The virtio spec v1.2 in 5.2.6.2 requires that the device
MUST return VIRTIO_BLK_S_UNSUPP for discard commands if the
unmap flag is set or if any unknown flag is set.

The discard handler was not reading the flags field at all,
silently accepting requests with arbitrary flags. Read and
validate the flags, rejecting any non-zero value with
VIRTIO_BLK_S_UNSUPP.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-31 13:11:15 +00:00
CMGS
ca58685f4c vmm: call notify_guest_clock_paused for Hyper-V guests
Previously, KVM_KVMCLOCK_CTRL was skipped when kvm_hyperv=on because
Windows does not use pvclock directly. However, KVM internally uses
pvclock data structures as the basis for computing the Hyper-V
Reference TSC page parameters. Not calling KVM_KVMCLOCK_CTRL means
there is no mechanism to signal time discontinuity to Windows guests
after pause/resume, contributing to multi-minute hangs.

Remove the kvm_hyperv guard so all guests receive the clock-paused
notification.

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-03-31 13:09:30 +00:00
Tobias Kässer
082fdc4d07 virtio-devices: fix guest-triggerable panic via OOB queue_select
The queue_msix_vector register (offset 0x1a in virtio PCI common
config) was indexed into the msix_queues Vec using the guest-controlled
queue_select value without bounds checking. A malicious guest can set
queue_select to any u16 value via offset 0x16, then read or write
offset 0x1a to trigger an out-of-bounds panic, crashing the VMM.

Replace direct Vec indexing with .get()/.get_mut() for bounds-checked
access, returning VIRTQ_MSI_NO_VECTOR (0xFFFF) on OOB reads to match
the virtio "no vector" sentinel. Add a regression test that sets
queue_select to 0xFFFF and exercises both the read and write paths.

AI/LLM disclosure: this patch was co-authored with Claude Code.

Fixes #7917
Signed-off-by: Tobias Kässer <t.kaesser@gmail.com>
2026-03-31 12:27:29 +00:00
Souradeep Chakrabarti
7832401816 hypervisor: Add GHCB CPUID, MSR and TERM_REQ handlers
When booting an SEV-SNP guest VM using IGVM with -pvalidate_opt 1 (lazy
page acceptance), the guest kernel's #VC exception handler may issue
VMGEXIT with SVM_EXIT_CPUID (0x72) or SVM_EXIT_MSR (0x7c) exit codes
via the GHCB page protocol. The hypervisor had no handlers for these
exit codes, causing the guest's #VC handler to fail and trigger
sev_es_terminate(), which sends GHCB_MSR_TERM_REQ (0x100). The
hypervisor then panicked on the unhandled 0x100 operation.

Add the following handlers to the GHCB VMGEXIT processing:

- SVM_EXIT_CPUID (0x72): Read function/index/xcr0/xss from the GHCB
  page and return CPUID results via get_cpuid_values().
- SVM_EXIT_MSR (0x7c): Handle MSR read (RDMSR) and write (WRMSR)
  requests from the guest via the GHCB page protocol.
- GHCB_MSR_TERM_REQ (0x100): Decode reason_set and reason_val from the
  GHCB MSR and return an error instead of panicking, allowing graceful
  error propagation.

Testing:

  Reproducer (on Azure DC16as_cc_v5, /dev/mshv):

  cloud-hypervisor --cpus boot=1,nested=off --memory size=512M \
    --disk path=osdisk.img path=cloudinit \
    --net "tap=,mac=12:34:56:78:90:06,ip=192.168.6.1,mask=255.255.255.128" \
    --serial null --console pty \
    --api-socket /tmp/ch.sock \
    --igvm /igvm_files/linux-ttyS0.bin \
    --host-data <hash> --platform sev_snp=on -v

  Before fix:

    thread 'vcpu0' panicked at hypervisor/src/mshv/mod.rs:1207:30:
    Unsupported VMGEXIT operation: 100

  After fix: VM boots successfully to login prompt with no panics.
  All virtio devices (console, rng, disks) activate normally.

No regression risk for non-SEV-SNP: all new code is within the
HVMSG_X64_SEV_VMGEXIT_INTERCEPT handler which is only reached for
SEV-SNP guests.

Signed-off-by: Souradeep Chakrabarti <schakrabarti@microsoft.com>
2026-03-31 11:32:34 +00:00
CMGS
ff20f18364 vmm: restore KVM clock before resuming vCPUs
Reorder resume() to: set_clock, device_manager.resume,
cpu_manager.resume. This matches the inverse of pause()
which correctly saves the clock before pausing vCPUs.

Signed-off-by: CMGS <ilskdw@gmail.com>
2026-03-31 08:39:20 +00:00
Wei Liu
c52e151439 github: Introduce cool down periods for non-rust-vmm crates
This gives the community more time to react to possible security chain
compromises.

We have high confidence that rust-vmm crates are trusted, and the
community is fully capable of spotting any issues. There is no need to
delay that group.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-03-31 09:24:34 +01:00
dependabot[bot]
c943103f75 build(deps): bump the non-rust-vmm group across 2 directories with 7 updates
Bumps the non-rust-vmm group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [uuid](https://github.com/uuid-rs/uuid) | `1.22.0` | `1.23.0` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.47` | `0.8.48` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.57` | `1.2.58` |
| libredox | `0.1.14` | `0.1.15` |
| [simd-adler32](https://github.com/mcountryman/simd-adler32) | `0.3.8` | `0.3.9` |

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

| Package | From | To |
| --- | --- | --- |
| [uuid](https://github.com/uuid-rs/uuid) | `1.22.0` | `1.23.0` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.47` | `0.8.48` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.57` | `1.2.58` |
| [simd-adler32](https://github.com/mcountryman/simd-adler32) | `0.3.8` | `0.3.9` |
| [winnow](https://github.com/winnow-rs/winnow) | `1.0.0` | `1.0.1` |



Updates `uuid` from 1.22.0 to 1.23.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.22.0...v1.23.0)

Updates `zerocopy` from 0.8.47 to 0.8.48
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.47...v0.8.48)

Updates `cc` from 1.2.57 to 1.2.58
- [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.57...cc-v1.2.58)

Updates `libredox` from 0.1.14 to 0.1.15

Updates `simd-adler32` from 0.3.8 to 0.3.9
- [Changelog](https://github.com/mcountryman/simd-adler32/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mcountryman/simd-adler32/commits/v0.3.9)

Updates `zerocopy-derive` from 0.8.47 to 0.8.48
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.47...v0.8.48)

Updates `uuid` from 1.22.0 to 1.23.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.22.0...v1.23.0)

Updates `zerocopy` from 0.8.47 to 0.8.48
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.47...v0.8.48)

Updates `cc` from 1.2.57 to 1.2.58
- [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.57...cc-v1.2.58)

Updates `simd-adler32` from 0.3.8 to 0.3.9
- [Changelog](https://github.com/mcountryman/simd-adler32/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mcountryman/simd-adler32/commits/v0.3.9)

Updates `winnow` from 1.0.0 to 1.0.1
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v1.0.0...v1.0.1)

Updates `zerocopy-derive` from 0.8.47 to 0.8.48
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.47...v0.8.48)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.48
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.58
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libredox
  dependency-version: 0.1.15
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: simd-adler32
  dependency-version: 0.3.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.48
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.48
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.58
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: simd-adler32
  dependency-version: 0.3.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: winnow
  dependency-version: 1.0.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.48
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 01:15:56 +00:00
Anatol Belski
5ca6ff869f block: vhd: Remove legacy async_io::DiskFile impl from FixedVhdDiskSync
No remaining consumers after switching to DiskBackend::Next.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
c90f5a9e47 vmm: Switch fixed VHD sync to DiskBackend::Next
Wire FixedVhdDiskSync through the new composable trait system.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
cc2f878094 block: vhd: impl AsyncDiskFile for FixedVhdDiskSync
Delegate try_clone() to FixedVhd::clone() and new_async_io() to
FixedVhdSync, preserving DiskFileError::NewAsyncIo.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
ce1592d1df block: vhd: impl DiskFile for FixedVhdDiskSync
Marker impl bundling DiskSize and Geometry supertraits.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
60af47c99b block: vhd: impl Resizable for FixedVhdDiskSync
Fixed VHD does not support resize, return UnsupportedFeature.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
74b0613c04 block: vhd: impl SparseCapable for FixedVhdDiskSync
Fixed VHD does not support sparse operations.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
1a56cb3d0e block: vhd: impl Geometry for FixedVhdDiskSync
Use default DiskTopology with 512byte sectors.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
dfa9a25fd8 block: vhd: impl DiskFd for FixedVhdDiskSync
Delegate to FixedVhd::as_raw_fd() for the backing file descriptor.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
266c5fc241 block: vhd: impl PhysicalSize for FixedVhdDiskSync
Delegate to FixedVhd::physical_size() which calls file.metadata().
Preserve the crate::Error::GetFileMetadata variant as the BlockError
source for diagnostic chain traversal.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
7136744549 block: vhd: impl DiskSize for FixedVhdDiskSync
Delegate to FixedVhd::logical_size() which returns the guest
visible capacity parsed from the VHD footer at construction.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
24e9049280 block: vhd: Switch FixedVhdDiskSync::new to BlockResult
Classify the io::Error as BlockErrorKind::Io with ErrorOp::Open.
Update vmm CreateFixedVhdDiskSync to take BlockError.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:18:09 +00:00
Anatol Belski
6e36932e90 block: vhdx: Remove legacy async_io::DiskFile impl from VhdxDiskSync
All functionality now provided by composable disk_file traits.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
b703043f77 vmm: Switch VHDX to DiskBackend::Next
Wire VhdxDiskSync through DiskBackend::Next instead of Legacy.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
cf9496376f block: vhdx: impl AsyncDiskFile for VhdxDiskSync
try_clone() shares the Arc<Mutex<Vhdx>>. new_async_io() creates
VhdxSync with a cloned Arc (no error path, infallible).

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
e99ad15939 block: vhdx: impl DiskFile for VhdxDiskSync
Supertrait marker, all component traits already implemented.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
ffdc8c49d1 block: vhdx: impl Resizable for VhdxDiskSync
Returns UnsupportedFeature, VHDX resize is not supported.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
3e904d7a27 block: vhdx: impl SparseCapable for VhdxDiskSync
VHDX does not support sparse operations.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
62244f94da block: vhdx: impl Geometry for VhdxDiskSync
Uses default topology.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
eca9e14ecb block: vhdx: impl DiskFd for VhdxDiskSync
Returns the raw fd from the inner Vhdx via mutex lock.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
5578f329cf block: vhdx: impl PhysicalSize for VhdxDiskSync
Explicitly matches GetFileMetadata to preserve the original error type.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
3164b66cb5 block: vhdx: impl DiskSize for VhdxDiskSync
Delegates to Vhdx::virtual_disk_size() which is infallible.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
b8b32f5927 block: vhdx: Switch VhdxDiskSync::new to BlockResult
Wrap VhdxError via BlockError::new(Io, e).with_op(Open). Update VMM
CreateFixedVhdxDiskSync error variant from VhdxError to BlockError.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:17:01 +00:00
Anatol Belski
019aa52830 block: vhd: Remove legacy async_io::DiskFile impl from FixedVhdDiskAsync
No remaining consumers after switching to DiskBackend::Next.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
b69bd219fa vmm: Switch fixed VHD async to DiskBackend::Next
Wire FixedVhdDiskAsync through the new composable trait system.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
c0db1f61ac block: vhd: impl AsyncDiskFile for FixedVhdDiskAsync
Delegate try_clone() to FixedVhd::clone() and new_async_io() to
FixedVhdAsync, preserving DiskFileError::NewAsyncIo.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
be6ce5b878 block: vhd: impl DiskFile for FixedVhdDiskAsync
Marker impl bundling DiskSize and Geometry supertraits.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
df5d2d64b2 block: vhd: impl Resizable for FixedVhdDiskAsync
Fixed VHD does not support resize, return UnsupportedFeature.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
0dece29fa8 block: vhd: impl SparseCapable for FixedVhdDiskAsync
Fixed VHD does not support sparse operations.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
1db64f511e block: vhd: impl Geometry for FixedVhdDiskAsync
Use default DiskTopology with 512 byte sectors.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
2973987c81 block: vhd: impl DiskFd for FixedVhdDiskAsync
Delegate to FixedVhd::as_raw_fd().

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
42af5913d9 block: vhd: impl PhysicalSize for FixedVhdDiskAsync
Fix .unwrap() bug: use explicit GetFileMetadata match.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
d82b1101d9 block: vhd: impl DiskSize for FixedVhdDiskAsync
Delegate to infallible FixedVhd::logical_size().

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Anatol Belski
15073edf08 block: vhd: Switch FixedVhdDiskAsync::new to BlockResult
Map FixedVhd::new io::Error to BlockError with ErrorOp::Open.
Update vmm CreateFixedVhdDiskAsync source type accordingly.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 22:16:04 +00:00
Muminul Islam
38eb10d209 block: Remove legacy DiskFile impl from RawFileDiskSync
Remove the legacy async_io::DiskFile implementation from
RawFileDiskSync now that the new disk_file trait impls are
in place.

Remove unused imports: Seek, SeekFrom, DiskFile, and
DiskFileResult.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
4f44cd9ed3 vmm: Switch RawFileDiskSync to DiskBackend::Next
Update device_manager.rs to construct DiskBackend::Next
instead of DiskBackend::Legacy for the synchronous raw
backend.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
afd018f9d1 fuzz: Switch RawFileDiskSync to DiskBackend::Next
Update fuzz/block.rs to construct DiskBackend::Next instead
of DiskBackend::Legacy for the synchronous raw backend.

Remove the unused async_io::DiskFile import.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
662d350cf9 block: Implement AsyncDiskFile trait for RawFileDiskSync
Add disk_file::AsyncDiskFile trait implementation for
RawFileDiskSync with try_clone() and new_async_io() methods.

try_clone() duplicates the underlying file descriptor and
wraps it in a new RawFileDiskSync. new_async_io() creates a
RawFileSync (synchronous fallback) backend, wrapping errors
in BlockError instead of DiskFileError.

Add DiskFileError::Clone variant in async_io.rs for the
try_clone() error path.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
da72a3abfb block: Implement DiskFile marker trait for RawFileDiskSync
Add empty disk_file::DiskFile impl for RawFileDiskSync.
This marker supertrait requires DiskSize + Geometry + Sync,
all of which are now satisfied.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
6b14d27f30 block: Implement Resizable trait for RawFileDiskSync
Add disk_file::Resizable trait implementation for
RawFileDiskSync. Calls file.set_len(size) and wraps the
I/O error in BlockError on failure.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
fe39929a2a block: Implement SparseCapable trait for RawFileDiskSync
Add disk_file::SparseCapable trait implementation for
RawFileDiskSync. Delegates to probe_sparse_support() to
detect whether the underlying file supports hole-punching.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
aa5aa6fe89 block: Implement Geometry trait for RawFileDiskSync
Add disk_file::Geometry trait implementation for
RawFileDiskSync. Probes disk topology from the file,
falling back to defaults on failure. Takes &self instead
of &mut self and uses unwrap_or_else for cleaner error
handling.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
bd026ce4cb block: Implement DiskFd trait for RawFileDiskSync
Add disk_file::DiskFd trait implementation for
RawFileDiskSync. Delegates to file.as_raw_fd() via
BorrowedDiskFd, taking &self instead of &mut self.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
744c365faa block: Implement PhysicalSize trait for RawFileDiskSync
Add disk_file::PhysicalSize trait implementation for
RawFileDiskSync. Returns metadata().len() wrapped in
BlockError on failure, consistent with the DiskSize impl.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
00a355d273 block: Implement DiskSize trait for RawFileDiskSync
Add disk_file::DiskSize trait implementation for
RawFileDiskSync using BlockError and BlockResult. Uses
metadata().len() instead of seek(SeekFrom::End(0)), taking
&self instead of &mut self.

Add BlockError, BlockErrorKind, BlockResult, and disk_file
imports needed by this and subsequent trait impls.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
98fedac77f block: Derive Debug on RawFileDiskSync
Add #[derive(Debug)] to RawFileDiskSync. This is required
by the new disk_file traits which have Send + Debug bounds.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:15:31 +00:00
Muminul Islam
573f3af77d vmm: Switch RawFileDisk to DiskBackend::Next
Update device_manager.rs to construct DiskBackend::Next
instead of DiskBackend::Legacy for the io_uring raw backend.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
491c5493c5 block: Implement AsyncDiskFile trait for RawFileDisk
Add disk_file::AsyncDiskFile trait implementation for
RawFileDisk with try_clone() and new_async_io() methods.

try_clone() duplicates the underlying file descriptor and
wraps it in a new RawFileDisk. new_async_io() creates a
RawFileAsync (io_uring) backend, wrapping errors in
BlockError instead of DiskFileError.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
607a2c769d block: Implement DiskFile marker trait for RawFileDisk
Add empty disk_file::DiskFile impl for RawFileDisk. This
marker supertrait requires DiskSize + Geometry + Sync, all
of which are now satisfied.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
64b65caddd block: Implement Resizable trait for RawFileDisk
Add disk_file::Resizable trait implementation for
RawFileDisk. Calls file.set_len(size) and wraps the I/O
error in BlockError on failure.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
899dec4c04 block: Implement SparseCapable trait for RawFileDisk
Add disk_file::SparseCapable trait implementation for
RawFileDisk. Delegates to probe_sparse_support() to detect
whether the underlying file supports hole-punching.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
7e514d8d0e block: Implement Geometry trait for RawFileDisk
Add disk_file::Geometry trait implementation for RawFileDisk.
Probes disk topology from the file, falling back to defaults
on failure. Takes &self instead of &mut self and uses
unwrap_or_else for cleaner error handling.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
ca1f48a717 block: Implement DiskFd trait for RawFileDisk
Add disk_file::DiskFd trait implementation for RawFileDisk.
Delegates to file.as_raw_fd() via BorrowedDiskFd, taking
&self instead of &mut self.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
aa6a7aea0e block: Implement PhysicalSize trait for RawFileDisk
Add disk_file::PhysicalSize trait implementation for
RawFileDisk. Returns metadata().len() wrapped in BlockError
on failure, consistent with the DiskSize impl.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
0647202215 block: Implement DiskSize trait for RawFileDisk
Add disk_file::DiskSize trait implementation for RawFileDisk
using BlockError and BlockResult. Uses metadata().len()
instead of seek(SeekFrom::End(0)), taking &self instead of
&mut self.

Add BlockError, BlockErrorKind, BlockResult, and disk_file
imports needed by this and subsequent trait impls.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
4586ca133a block: Derive Debug on RawFileDisk
Add #[derive(Debug)] to RawFileDisk. This is required by the
new disk_file traits which have Send + Debug bounds.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Muminul Islam
6261cad0d8 block: Add DiskFileError::Clone variant
Add a Clone variant to DiskFileError for error handling in
the upcoming AsyncDiskFile::try_clone() implementations.

This variant will be used by RawFileDisk and RawFileDiskSync
when cloning the underlying file descriptor fails.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-30 22:14:42 +00:00
Anatol Belski
823ab99e02 virtio-devices: block: Use error specific status in sync fallback path
The sync fallback path in process_queue_submit() hardcoded
VIRTIO_BLK_S_IOERR for all errors. This caused unsupported
request errors to report IOERR to the guest instead of the
correct VIRTIO_BLK_S_UNSUPP.

Use ExecuteError::status() to return the appropriate status
code for each error variant.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-30 21:21:19 +00:00
Anirudh Rayabharam
cbe7018f84 vmm: cpu: fix broken URL
Fix broken URL reported by Lychee. The binutils-gdb github repo no
longer exists. Use the equivalent sourceware.org link.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-03-30 17:21:15 +00:00
Demi Marie Obenour
9b9950335f ci: Run pull_request_target workflows with no permisisons
The MSHV tests need access to secrets so that they can run workloads in
Azure.  It does not need privileged access to GitHub.  Ensure its
GITHUB_TOKEN has no permissions.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-03-30 17:21:08 +00:00
Bo Chen
ea0a0393b5 build: Fix the vfio integration test workflow
Switch the workspace ownership fix from 'runner' to 'github-runner'
to match the new dedicated service account used on the self-hosted
VFIO runner.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-03-28 08:20:08 +00:00
Bo Chen
ce3b3fa1b2 build: Enable the vfio CI worker
This reverts commit 8aaf3734aa.

Fixes: #7751

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-03-28 08:20:08 +00:00
Rob Bradford
baacaea35e build: Bump vfio-ioctls to 0.6.0
This version is identical but with a new version number as the old
version is yanked due to a semver break.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-28 08:19:31 +00:00
Philipp Schuster
c06f6c3293 tests: fix CI flakiness
This test has failed at least once in upstream CI. With the applied
stress workload, reducing the downtime to 1 ms makes it virtually
impossible for CI runners with various speeds to complete VM migration.

In other words: we will always be able to cancel.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-03-27 16:29:28 +00:00
Rob Bradford
0bc3ca1103 tests: Add snapshot/restore integration test for virtio-fs
Add test_snapshot_restore_virtio_fs which validates that virtio-fs
continues to work correctly across a snapshot/restore cycle.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Rob Bradford
cd3334a3c2 virtio-devices: vhost_user: Enable snapshot/restore for vhost-user-*
Enable the DEVICE_STATE protocol feature negotiation for all vhost-user
devices (block, fs, net, and generic). Restoring the state (including
the backend state if present) and vrings.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Rob Bradford
2fbb98e2c3 virtio-devices: vhost_user: Add common code for restoring state
Add a common method for validating the state (checking vrings &
device_state) and then restoring the backend state if present.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Rob Bradford
02688993a0 virtio-devices: vhost_user: Fetch and store the backend state/vring
Fetch the opaque device state from the backend and store it along with
the last vring used in the state used for the snapshot.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Rob Bradford
a6b9455402 virtio-devices: vhost_user: Add get/set of backend state support
The vhost-user protocol now has support for the backend to provide an
opaque blob of data (read or written through a pipe) that the VMM can
use to save/restore state after snapshot/restore or live migration.

It also adds a command for checking the backend accepts the uploaded
device state. One quirk of saving the state is that GET_VRING_BASE must
be used first to quiesce the state of the backend and flush any
in-flight requests. This then also requires saving that index for use on
the restore.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Rob Bradford
afc3cb8e1f virtio-devices: vhost_user: Rename update_supports_migration
Rename update_supports_migration() to update_supported_features() as
this method will be extended to track additional capability flags beyond
just migration support (e.g. DEVICE_STATE for snapshot/restore.)

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Rob Bradford
8536a2536e virtio-devices: vhost_user: Adapt state() to return Result
This is a refactoring step in preparation for fetching backend device
state via SET_DEVICE_STATE_FD which can fail.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Rob Bradford
3e233af654 virtio-devices: vhost_user: Access acked_protocol_features directly
In GenericVhostUser's read_config and write_config, access
vu_common.acked_protocol_features directly instead of going through
the state() method which creates a struct. This removes creating the
struct just to access two fields that are already directly accessible.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Rob Bradford
bd56214d54 virtio-devices: vhost_user: Add VhostUserCommon::state() helper
With a common state structure for all vhost-user devices the state()
methods can also be refactored for reuse. This will make it easier to
add new common fields in the future for snapshot/restore.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Rob Bradford
a77f89847e virtio-devices: vhost_user: Consolidate device State into VhostUserState
Each vhost-user device type had near identical State structs.
Deduplicate those by introducing a new common struct (and parameterising
it when it needs to embed a config member.) This will make it easier to
reuse more code in the future and to add more struct members to handle
the requirements of snapshot/restore.

These changes have been designed to have no impact on the existing
snapshot/restore state.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-27 14:18:27 +00:00
Alyssa Ross
e1e6d0a25b vmm: fix rebooting with landlock and pty console
When landlock support was added, creation of file descriptors was
moved out into a function called pre_create_console_devices, with the
idea being that this could be run before Landlock rules are applied
and access to all the necessary paths are dropped.

This idea didn't take reboots into account, though.  When a VM is
rebooted, pre_create_console_devices is called again, but now the
Landlock rules have been applied, so they need to allow access to all
those paths anyway.

I imagine the way this was intended to work was that file descriptors
would be preserved across reboot, but that's not currently the case,
and it's not a trivial change to make because they get dropped when
the VM is destroyed.  Longer term it would be ideal if Cloud
Hypervisor's implementation was more focused on file descriptors than
paths[1], and if created VMs only took references to file descriptors,
so they were easily preserved across reboots.

Fixes: b3e5738b4 ("vmm: Introduce ApplyLandlock trait")
Closes: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7547
Link: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7704 [1]
Signed-off-by: Alyssa Ross <hi@alyssa.is>
2026-03-27 11:11:26 +00:00
Anatol Belski
c21d6bb856 block: Add unit tests for query_device_size()
Test regular file with odd size (not page/sector aligned), sparse
file with punch hole verifying physical < logical, and char device
rejection, etc.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-27 11:05:07 +00:00
Anatol Belski
a21b9588ec virtio-devices: block: Use logical_size() for advisory lock range
Use logical_size() instead of physical_size() for the byte-range
advisory lock. physical_size() returns st_blocks*512 which is the
actual host allocation and can be smaller than the guest visible
extent on sparse files, leaving part of the range unprotected.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-27 11:05:07 +00:00
Anatol Belski
8b6eb83f2a block: qcow: raw_file: Use query_device_size() for size queries
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>
2026-03-27 11:05:07 +00:00
Anatol Belski
1b92af5534 block: raw_async_aio: Use query_device_size() for size queries
Use query_device_size() instead of seek(End(0)) and metadata().len()
to correctly handle block device and regular file handles.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-27 11:05:07 +00:00
Anatol Belski
290f57a6e3 block: raw_sync: Use query_device_size() for size queries
Use query_device_size() instead of seek(End(0)) and metadata().len()
to correctly handle block device and regular file handles.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-27 11:05:07 +00:00
Anatol Belski
f14e2d2b40 block: raw_async: Use query_device_size() for size queries
Use query_device_size() instead of seek(End(0)) and metadata().len()
to correctly handle block device and regular file handles.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-27 11:05:07 +00:00
Anatol Belski
051a6eff5c block: Add query_device_size() for file and block device size
Add a shared helper that returns the logical size in bytes for both
regular files and block devices using an immutable &File reference.

Regular files use metadata().len(). Block devices use the BLKGETSIZE64
ioctl. Any other file type returns an InvalidInput error.

This avoids seek(SeekFrom::End(0)) which requires &mut self and can
return incorrect results for block devices when the file position is
in an unexpected state.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-27 11:05:07 +00:00
Anatol Belski
c5dba9ea3b vmm: seccomp: Allow BLKGETSIZE64 ioctl
Add BLKGETSIZE64 0x80081272 to the VMM seccomp ioctl allow list
alongside the existing BLK* ioctls. This is needed for querying
block device size without seeking.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-27 11:05:07 +00:00
Saravanan D
9156758828 pci: clamp sparse mmap holes to physical BAR
For VFIO devices with non page aligned MSI-X offsets,
fixup_msix_region() relocates MSI-X table and PBA offsets
into an enlarged virtual BAR by mutating msix.cap in place.
generate_sparse_areas() later reads those relocated offsets
to carve mmap holes, but receives the physical BAR size as
region_size. The relocated offsets exceed the physical BAR
boundary, and the kernel rejects the mmap with EINVAL.

Guard inter_ranges insertion with an offset < region_size
check so relocated entries are skipped. The full physical
BAR is mmapped as a single region. The relocated MSI-X in
the upper half of the virtual BAR remains trapped because
it has no mmap backing.

Linux kernel commit a32295c612c5 ("vfio-pci: Allow mapping
MSIX BAR") allows mmapping the entire BAR including the
MSI-X region when VFIO_REGION_INFO_CAP_MSIX_MAPPABLE is
advertised. The actual security guarantees come from IOMMU
isolation and interrupt remapping, not from filtering
MSI-X table accesses. QEMU follows the same pattern,
mmapping the entire physical BAR when MsixMappable is
present.

Fixes: #7898

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-03-27 10:55:59 +00:00
Bo Chen
1bc49758a0 pci, vmm: Cleanup the naming and references to VFIO container
Following the `VfioContainer` to `VfioOps` trait switch, update the
remaining field names, method names, comments, and log messages to use
`vfio_ops` and "host IOMMU address space" consistently.

No fucntional changes.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-03-27 04:22:05 +00:00
Bo Chen
7360bfe33a pci, vmm: Switch to use more generic VfioOps trait
Replace the concrete `VfioContainer` type with the `VfioOps` trait
object for device passthrough. This decouples the VFIO DMA mapping
interface from the legacy VFIO container/group implementation, allowing
it to be extended to support VFIO cdev and iommufd in the future.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-03-27 04:22:05 +00:00
Muminul Islam
5bb4ea20a5 scripts: Fix volume argument splitting in dev_cli.sh
Use IFS-based splitting instead of parameter expansion to
correctly separate '#'-delimited volume paths in
process_volumes_args(). The previous approach placed all
volumes into a single array element, causing Docker to
receive malformed --volume arguments.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-27 03:39:10 +00:00
Rob Bradford
57e766bdbb virtio-devices: Only try and activate if the device became ready
Previously this code could lead to the device being trying to be
activated multiple times as the code to trigger the activation was based
on the state of the device (not yet activated and device being ready).
This could occur if anothe vCPU wrote to a PCI BAR on this device before
the device activation was completed by the VMM thread. Now we only
trigger the activation if the device readiness has changed as a result
of this BAR write (by checking that the readiness was originally
unready.)

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-26 17:20:09 +00:00
Dylan Reid
65073259c6 vmm: handle malformed balloon actual from guest
The actual size of the balloon is taken directly from the guest. A
misbehaving guest can set it to an arbitrary value and cause underflow
on the next vm.info call. Use a saturation_sub instead to avoid a panic
in a debug build or a crazy number in a release build.

Signed-off-by: Dylan Reid <dgreid@dylanreid.com>
2026-03-26 08:54:54 +00:00
Alyssa Ross
0b90180266 vmm: use UAPI name for PIDTYPE_PID
As far as I can tell, PIDTYPE_PID is a kernel-internal name, and
PR_SCHED_CORE_SCOPE_THREAD is the UAPI name.  There's no PIDTYPE_PID
in the UAPI headers, and the core scheduling documentation says that
the fourth prctl argument should be a "PR_SCHED_CORE_SCOPE_-prefixed
macro constant".

Link: https://www.kernel.org/doc/html/v6.19/admin-guide/hw-vuln/core-scheduling.html#usage
Fixes: 3f800d2bb ("vmm: Add core scheduling support for vCPU threads")
Signed-off-by: Alyssa Ross <hi@alyssa.is>
2026-03-25 16:36:29 +00:00
Rob Bradford
a8d962640f build: Use newer virtiofsd
Switch to a git hash of virtiofsd as unfortunately the last release is
some time ago and does not incorporate a dependency bump that enables it
to work with snapshot-restore.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-25 15:19:56 +00:00
Rob Bradford
11d8ac48cb build: Use released vfio & vhost crates
Replace the git dependencies with newly released versions

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-25 13:48:34 +00:00
Muminul Islam
80ce696006 tests: add CVM test for vdpa_block
Add a CVM integration test for vdpa_block that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro and with_cpu(2).

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
7e62565cca tests: add CVM test for macvtap_hotplug
Add a CVM integration test for macvtap_hotplug that validates
the same functionality using a confidential guest with the
basic_cvm_guest macro and with_cpu(2).

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
b7bd19b24a tests: add CVM test for macvtap
Add a CVM integration test for macvtap that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro and with_cpu(2).

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
6fa6ead2b3 tests: add CVM test for tap_from_fd
Add a CVM integration test for tap_from_fd that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro and with_cpu(2).

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
7d672872d6 tests: add CVM test for pvpanic
Add a CVM integration test for pvpanic that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
5ad9822a50 tests: add CVM test for watchdog
Add a CVM integration test for watchdog that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro and FOCAL_IMAGE_NAME.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
81f26bd995 tests: add CVM test for counters
Add a CVM integration test for counters that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
9a88b9a42e tests: add CVM test for net_hotplug
Add a CVM integration test for net_hotplug that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
60abdf0ea1 tests: add CVM test for disk_hotplug
Add a CVM integration test for disk_hotplug that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
9564b4f278 tests: add CVM test for landlock
Add a CVM integration test for landlock that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
833d1d39a5 tests: add CVM test for memory_overhead
Add a CVM integration test for memory_overhead that validates
the same functionality using a confidential guest with the
basic_cvm_guest macro and custom memory size.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
0c477c12ec tests: add CVM test for pci_bar_reprogramming
Add a CVM integration test for pci_bar_reprogramming that
validates the same functionality using a confidential guest
with the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
f4df95c142 tests: add CVM test for direct_kernel_boot_noacpi
Add a CVM integration test for direct_kernel_boot_noacpi
that validates the same functionality using a confidential
guest with the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
a4f1c65b14 tests: add CVM test for console_file
Add a CVM integration test for console_file that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
e9d8ecf38a tests: add CVM test for virtio_console
Add a CVM integration test for virtio_console that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
36871b3547 tests: add CVM test for serial_off
Add a CVM integration test for serial_off that validates
the same functionality using a confidential guest with
the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
108391f720 tests: add CVM test for multiple_network_interfaces
Add a CVM integration test for multiple_network_interfaces
that validates the same functionality using a confidential
guest with the basic_cvm_guest macro.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
e89b584694 tests: extract _test_vdpa_block to tests_wrappers
Extract test logic from test_vdpa_block into a shared
_test_vdpa_block wrapper function in tests_wrappers.rs.
The vdpa module check is kept in the parent test case.
Update the parent to use basic_regular_guest macro with
with_cpu(2). The wrapper uses default_kernel_cmdline()
for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
205bb5ff5e tests: extract _test_macvtap to tests_wrappers
Extract test logic from _test_macvtap into a shared wrapper
function in tests_wrappers.rs. Update both test_macvtap and
test_macvtap_hotplug to use the basic_regular_guest macro
with with_cpu(2). The wrapper uses default_kernel_cmdline()
for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
a3423a4483 tests: extract _test_tap_from_fd to tests_wrappers
Extract test logic from test_tap_from_fd into a shared
_test_tap_from_fd wrapper function in tests_wrappers.rs.
Update the parent test case to use the basic_regular_guest
macro with with_cpu(2). The wrapper uses
default_kernel_cmdline() for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
17774ee564 tests: extract _test_pvpanic to tests_wrappers
Extract test logic from test_pvpanic into a shared
_test_pvpanic wrapper function in tests_wrappers.rs.
Update the parent test case to use the basic_regular_guest
macro. The wrapper uses default_kernel_cmdline() for
kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
93a3fc5b91 tests: extract _test_watchdog to tests_wrappers
Extract test logic from test_watchdog into a shared
_test_watchdog wrapper function in tests_wrappers.rs.
Update the parent test case to use the basic_regular_guest
macro with FOCAL_IMAGE_NAME. The wrapper uses
default_kernel_cmdline() for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
4b77ac26d1 tests: extract _test_counters to tests_wrappers
Extract test logic from test_counters into a shared
_test_counters wrapper function in tests_wrappers.rs.
Update the parent test case to use the basic_regular_guest
macro. The wrapper uses default_kernel_cmdline() for
kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
7284c02d31 tests: extract _test_net_hotplug to tests_wrappers
Extract test logic from _test_net_hotplug into a shared
wrapper function in tests_wrappers.rs. Update both
test_net_hotplug and test_net_multi_segment_hotplug to
use the basic_regular_guest macro. The wrapper uses
default_kernel_cmdline() for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
ef81bb1358 tests: extract _test_virtio_block_topology wrapper
Extract test logic from test_virtio_block_topology into
a shared _test_virtio_block_topology wrapper function in
tests_wrappers.rs. The loop device creation and cleanup
are kept in the parent test case. The wrapper uses
default_kernel_cmdline() for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
8f40aed0ca tests: extract _test_disk_hotplug to tests_wrappers
Extract test logic from _test_disk_hotplug into a shared
wrapper function in tests_wrappers.rs. Update both
test_disk_hotplug and test_disk_hotplug_with_landlock
to use the basic_regular_guest macro. The wrapper uses
default_kernel_cmdline() for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
c1d929d0cf tests: extract _test_landlock to tests_wrappers
Extract test logic from test_landlock into a shared
_test_landlock wrapper function in tests_wrappers.rs.
Update the parent test case to use the basic_regular_guest
macro. The wrapper uses default_kernel_cmdline() for
kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
af7fabd2c6 tests: extract _test_memory_overhead to tests_wrappers
Extract test logic from test_memory_overhead into a shared
_test_memory_overhead wrapper function in tests_wrappers.rs.
The custom memory size is set in the parent test case via
with_memory(). The wrapper uses default_kernel_cmdline()
and default_memory() for setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
b478fa0ffd tests: extract _test_pci_bar_reprogramming wrapper
Extract test logic from test_pci_bar_reprogramming into
a shared _test_pci_bar_reprogramming wrapper function in
tests_wrappers.rs. The kernel_path selection for aarch64
(edk2) is kept in the parent test case. The wrapper uses
default_kernel_cmdline() for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
bff6e40eaa tests: extract _test_direct_kernel_boot_noacpi wrapper
Extract test logic from test_direct_kernel_boot_noacpi into
a shared _test_direct_kernel_boot_noacpi wrapper function in
tests_wrappers.rs. The kernel cmdline modification (acpi=off)
is kept in the parent test case. The wrapper uses
default_kernel_cmdline() for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
fb02146e2c tests: extract _test_console_file to tests_wrappers
Extract test logic from test_console_file into a shared
_test_console_file wrapper function in tests_wrappers.rs.
Update the parent test case to use the basic_regular_guest
macro. The wrapper uses default_kernel_cmdline() for
kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
dfd07ae6d0 tests: extract _test_virtio_console to tests_wrappers
Extract test logic from test_virtio_console into a shared
_test_virtio_console wrapper function in tests_wrappers.rs.
Update the parent test case to use the basic_regular_guest
macro. The wrapper uses default_kernel_cmdline() for
kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
77266daf86 tests: extract _test_multiple_network_interfaces wrapper
Extract test logic from test_multiple_network_interfaces into
a shared _test_multiple_network_interfaces wrapper function in
tests_wrappers.rs. Update the parent test case to use the
basic_regular_guest macro. The wrapper uses
default_kernel_cmdline() for kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
8d9e8ea6f8 tests: extract _test_serial_off to tests_wrappers
Extract test logic from test_serial_off into a shared
_test_serial_off wrapper function in tests_wrappers.rs.
Update the parent test case to use the basic_regular_guest
macro. The wrapper uses default_kernel_cmdline() for
kernel/cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
b7a7366ef9 tests: add CVM test for dmi_oem_strings
Add a CVM variant of the DMI OEM strings test in
integration_cvm.rs. This test creates a confidential
guest using the basic_cvm_guest! macro and invokes the
shared _test_dmi_oem_strings() wrapper to validate DMI
OEM strings behavior under CVM.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
c3976ccbda tests: extract _test_dmi_oem_strings to tests_wrappers
Move the DMI OEM strings test logic from integration.rs
into a shared _test_dmi_oem_strings() function in
tests_wrappers.rs. The original test in integration.rs
now uses the basic_regular_guest! macro and delegates to
this shared function, enabling reuse by CVM tests.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
43641cf714 tests: add CVM test for dmi_uuid
Add a CVM variant of the DMI UUID test in
integration_cvm.rs. This test creates a confidential
guest using the basic_cvm_guest! macro and invokes the
shared _test_dmi_uuid() wrapper to validate DMI UUID
behavior under CVM.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
dcb4d99a50 tests: extract _test_dmi_uuid to tests_wrappers
Move the DMI UUID test logic from integration.rs into a
shared _test_dmi_uuid() function in tests_wrappers.rs.
The original test in integration.rs now uses the
basic_regular_guest macro and delegates to this shared
function, enabling reuse by CVM tests.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
6f9794ded0 tests: use basic_regular_guest macro in test_api_http_create_boot
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_api_http_create_boot, chaining with_cpu(4) for the
required CPU count configuration.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
698084f3d7 tests: use basic_regular_guest macro in test_api_http_pause_resume
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_api_http_pause_resume, chaining with_cpu(4) for the
required CPU count configuration.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
1b3cd88b25 tests: use basic_regular_guest macro in test_api_http_delete
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_api_http_delete, chaining with_cpu(4) for the
required CPU count configuration.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
e5b589ee54 tests: use basic_regular_guest macro in test_api_http_shutdown
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_api_http_shutdown, chaining with_cpu(4) for the
required CPU count configuration.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
aead6dd59f tests: use basic_regular_guest macro in test_virtio_vsock_hotplug
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_virtio_vsock_hotplug. The aarch64 variant chains
with_kernel_path for EDK2 firmware support.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
b9c9e7265d tests: use basic_regular_guest macro in test_virtio_vsock
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_virtio_vsock for consistency and reduced boilerplate.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
a6724ba79f tests: use basic_regular_guest macro in test_direct_kernel_boot
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_direct_kernel_boot for consistency and reduced
boilerplate.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
84951127b7 tests: use basic_regular_guest macro in test_pci_multiple_segments
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_pci_multiple_segments for consistency and reduced
boilerplate.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
54c91b0281 tests: use basic_regular_guest macro in test_virtio_net_ctrl_queue
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_virtio_net_ctrl_queue for consistency and reduced
boilerplate.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
4c4cf940ec tests: use basic_regular_guest macro in test_pci_msi
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_pci_msi for consistency and reduced boilerplate.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
43731149c8 tests: use basic_regular_guest macro in test_power_button
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_power_button for consistency and reduced boilerplate.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
43fb142afb tests: use basic_regular_guest macro in test_virtio_queue_affinity
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_virtio_queue_affinity, chaining with_cpu(4) for the
required CPU count configuration.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
bcdbc875be tests: use basic_regular_guest macro in test_cpu_affinity
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_cpu_affinity, chaining with_cpu(2) for the required
CPU count configuration.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
b1a0ab4d1f tests: use basic_regular_guest macro in test_multi_cpu
Replace manual UbuntuDiskConfig and GuestFactory guest
creation with the basic_regular_guest! macro in
test_multi_cpu for consistency and reduced boilerplate.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
5f4ad4bb1e tests: extract _test_dmi_serial_number to tests_wrappers
Move the DMI serial number test logic from integration.rs
into a shared _test_dmi_serial_number() function in
tests_wrappers.rs. The original test now delegates to
this shared function, enabling reuse by CVM tests.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
b65a3a58f3 tests: add CVM test for split_irqchip
Add a CVM variant of the split IRQ chip test in
integration_cvm.rs. This test creates a confidential
guest and invokes the shared _test_split_irqchip()
wrapper to validate split IRQ chip behavior under CVM.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
56696ed901 tests: extract _test_split_irqchip to tests_wrappers
Move the split IRQ chip test logic from integration.rs
into a shared _test_split_irqchip() function in
tests_wrappers.rs. The original test now delegates to
this shared function, enabling reuse by CVM tests.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
2929cc9d16 tests: add CVM test for virtio_block_dynamic_vhdx_expand
Add a CVM variant of the virtio block dynamic VHDX expand
test in integration_cvm.rs. This test creates a
confidential guest and invokes the shared
_test_virtio_block_dynamic_vhdx_expand() wrapper to
validate VHDX expansion under CVM.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
ac8d4c2953 tests: extract _test_virtio_block_dynamic_vhdx_expand
Move the virtio block dynamic VHDX expand test logic from
integration.rs into a shared _test_virtio_block_dynamic_
vhdx_expand() function in tests_wrappers.rs. The original
test in integration.rs now calls this shared function,
enabling reuse by CVM tests.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
6aad6e40c8 tests: use basic_regular_guest macro in OVMF test
Refactor test_focal_ovmf to use the basic_regular_guest!
macro with the with_kernel() builder instead of manually
constructing UbuntuDiskConfig and setting kernel_path.
This also removes leftover unused disk_config in
test_focal_hypervisor_fw.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
0fc0f2bd0e tests: add basic_regular_guest macro and with_kernel
Introduce a basic_regular_guest! macro in integration.rs
to reduce boilerplate when creating regular (non-CVM)
guest instances. Also add a with_kernel() builder method
to Guest in test_infra, allowing fluent configuration of
the kernel path.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
7802470906 tests: add basic_cvm_guest macro for CVM tests
Introduce a basic_cvm_guest! macro in integration_cvm.rs
to reduce boilerplate when creating confidential VM guest
instances. This replaces repetitive UbuntuDiskConfig and
GuestFactory::new_confidential_guest_factory() calls
across multiple CVM test functions.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Muminul Islam
c75504c833 tests: move CVM tests to integration_cvm.rs
Move all CVM (Confidential VM) integration tests from
integration.rs into a dedicated integration_cvm.rs file.
This separation improves code organization and makes it
easier to manage CVM-specific test cases independently
from regular integration tests.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-25 13:34:59 +00:00
Philipp Schuster
56771a0f4c docs: remove trailing spaces in CONTRIBUTING.md
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-25 10:08:17 +00:00
Philipp Schuster
00b3a48900 docs: revisit AI/LLM policy in CONTRIBUTING.md
This summarizes the latest state all relevant parties have agreed on
in the last meeting.

This commit was rephrased and improved with the help of Claude Code
using the Sonnet 4.6 model.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-25 10:08:17 +00:00
Philipp Schuster
81495241eb tests: reduce memory pressure, fix ARM tests in CI
Mosts tests used 4GB of RAM, although the VM is mostly idling. In CI, we
experienced OOM issues on the ARM runners. If we reduce the VM memory of
the parallel live migration tests to 1.5GB RAM, we still have enough
capacity in the VM so that everything succeeds while reducing resource
usage.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-24 15:01:47 +00:00
Philipp Schuster
98fd139111 tests: add integration tests
This adds two new integration tests for the new functionality:

- VM under load, downtime=1ms, timeout=1s, timeout_strategy=cancel
- VM under load, downtime=1ms, timeout=1s, timeout_strategy=force

By using a short downtime and timeout plus adding a stress worker in the
guest, we can prevent quick migration. Therefore, we can nicely test the
timeout_strategy.

Testing for a specific downtime is cumbersome to do and highly depends
on CPU/host utilization. To prevent flakiness, there is no such test
integration test. I did, however, manual testing of that functionality.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-24 15:01:47 +00:00
Philipp Schuster
c8cee779b0 docs: update live_migration.md
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-24 15:01:47 +00:00
Philipp Schuster
49868f483e vmm: update openapi spec
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-24 15:01:47 +00:00
Philipp Schuster
49e03c8bc5 vmm: migration: Converge based on user-provided downtime and timeout
Wire the new `downtime_ms`, `timeout_s`, and `timeout_strategy` fields
from `VmSendMigrationData` into the precopy loop, replacing the previous
hard-coded 5-iteration cap.

Each iteration now evaluates three convergence criteria in order:
- no dirty pages remain;
- the estimated final-iteration downtime is within the configured budget
- or the overall migration timeout has elapsed.

On timeout, `TimeoutStrategy::Cancel` aborts and keeps the VM live on
the source, while `TimeoutStrategy::Force` proceeds regardless of the
downtime target. The convergence callback is updated to return a Result
to propagate the cancel error cleanly up the call stack.

With the recent changes [0], it is fairly easy to implement the new
checks and operate on actual metrics.

These changes are inspired by [1] but differ significantly in details.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7799
[1] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7033

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-24 15:01:47 +00:00
Philipp Schuster
bbb0f083b0 vmm: api: add configurable downtime and timeout to VmSendMigrationData
Management software needs fine-grained control over live migration to
meet QoS requirements for VM guests. Add `downtime_ms`, `timeout_s`, and
`timeout_strategy` fields to `VmSendMigrationData`, exposed via API.

This commit contains the API changes only; the VMM does not yet act on
these values. This follows in the next commit.

For the JSON API, downtime and timeout are represented as plain integers
(downtime_ms and timeout_s) to make the units explicit. Using Duration
directly would require custom (de)serialization logic, so instead the
internal raw integers are exposed as Duration via getters. This
introduces minor conversion overhead but keeps the Rust API clear and
unambiguous.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-24 15:01:47 +00:00
Philipp Schuster
040fcaed92 vmm: add VmSendMigrationData::parse(); integrate with OptionParser
This change prepares upcoming options (following commit) that are added
to VmSendMigrationData.

VmSendMigrationData is a special case as it is currently the only
"rich configuration" type that lives outside `config.rs`, as it is
purely API-facing. Therefore, it isn't integrated into the existing
OptionParser infrastructure. We therefore introduce a `parse()` method
to use that in `ch-remote` in the following.

In `ch-remote`, we remove `--local` for `send-migration` and switch to
the new option string parsing constructor (breaking change!). This
prepares the addition of downtime and timeout options in the following
and streamlines the `ch-remote` command line interface with other
commands, such as `ch-remote add-net`.

Lastly, this commit updates the integration tests.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-24 15:01:47 +00:00
dependabot[bot]
7e0f8f7163 build: Bump the non-rust-vmm group across 2 directories with 10 updates
Bumps the non-rust-vmm group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [env_logger](https://github.com/rust-cli/env_logger) | `0.11.9` | `0.11.10` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.8.2` | `1.9.0` |
| [env_filter](https://github.com/rust-cli/env_logger) | `1.0.0` | `1.0.1` |
| [terminal_size](https://github.com/eminence/terminal-size) | `0.4.3` | `0.4.4` |
| [toml_datetime](https://github.com/toml-rs/toml) | `1.0.1+spec-1.1.0` | `1.1.0+spec-1.1.0` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.25.5+spec-1.1.0` | `0.25.8+spec-1.1.0` |

Bumps the non-rust-vmm group with 3 updates in the /fuzz directory: [arc-swap](https://github.com/vorner/arc-swap), [toml_datetime](https://github.com/toml-rs/toml) and [toml_edit](https://github.com/toml-rs/toml).


Updates `env_logger` from 0.11.9 to 0.11.10
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.9...v0.11.10)

Updates `arc-swap` from 1.8.2 to 1.9.0
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.8.2...v1.9.0)

Updates `anstream` from 0.6.21 to 1.0.0
- [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.21...anstream-v1.0.0)

Updates `anstyle-parse` from 0.2.7 to 1.0.0
- [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-parse-v0.2.7...anstyle-parse-v1.0.0)

Updates `env_filter` from 1.0.0 to 1.0.1
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/env_filter-v1.0.0...env_filter-v1.0.1)

Updates `terminal_size` from 0.4.3 to 0.4.4
- [Release notes](https://github.com/eminence/terminal-size/releases)
- [Commits](https://github.com/eminence/terminal-size/compare/v0.4.3...v0.4.4)

Updates `toml_datetime` from 1.0.1+spec-1.1.0 to 1.1.0+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v1.0.1...toml_datetime-v1.1.0)

Updates `toml_edit` from 0.25.5+spec-1.1.0 to 0.25.8+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.25.5...v0.25.8)

Updates `toml_parser` from 1.0.10+spec-1.1.0 to 1.1.0+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.0.10...toml_parser-v1.1.0)

Updates `windows-sys` from 0.60.2 to 0.61.2
- [Release notes](https://github.com/microsoft/windows-rs/releases)
- [Commits](https://github.com/microsoft/windows-rs/commits)

Updates `arc-swap` from 1.8.2 to 1.9.0
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.8.2...v1.9.0)

Updates `toml_datetime` from 1.0.1+spec-1.1.0 to 1.1.0+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v1.0.1...toml_datetime-v1.1.0)

Updates `toml_edit` from 0.25.5+spec-1.1.0 to 0.25.8+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.25.5...v0.25.8)

Updates `toml_parser` from 1.0.10+spec-1.1.0 to 1.1.0+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.0.10...toml_parser-v1.1.0)

---
updated-dependencies:
- dependency-name: env_logger
  dependency-version: 0.11.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: anstream
  dependency-version: 1.0.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: anstyle-parse
  dependency-version: 1.0.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: env_filter
  dependency-version: 1.0.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: terminal_size
  dependency-version: 0.4.4
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_datetime
  dependency-version: 1.1.0+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.25.8+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_parser
  dependency-version: 1.1.0+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: windows-sys
  dependency-version: 0.61.2
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.9.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: toml_datetime
  dependency-version: 1.1.0+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.25.8+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_parser
  dependency-version: 1.1.0+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-24 01:06:16 +00:00
Sebastian Eydam
5aeb9f55d1 virtio-devices: fix barrier handling in virtio-blk
When configuring multiple queues for a virtio device, the guest can
activate between 1 and the configured amount of queues. The firmware,
for example, may activate only one queue, while a Linux guest would
likely activate all available queues.

The constructor of virtio-blk initializes the `paused_sync` barrier
using the configured queue count (plus one for the main thread). This
can be wrong if the guest enable a different number of queues at
activation time, which can make pause hang. Thus, we now recompute the
barrier size from the queues that are actually activated.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-03-23 14:04:58 +00:00
Sebastian Eydam
882f82f04b virtio-devices: fix barrier handling in virtio-net
When configuring multiple queues for a virtio device, the guest can
activate between 1 and the configured amout of queues. The firmware,
for example, may activate only one queue, while a Linux guest would
likely activate all available queues.

The constructor of virtio-net initializes the `paused_sync` barrier
using the configured queue count (plus one for the main thread). This
can be wrong if the guest enables a different number of queues at
activation time, which can make pause hang. Thus, we now recompute the
barrier size from the queues that are actually activated.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
2026-03-23 14:04:58 +00:00
Anatol Belski
92109136f1 block: Include actual segment count in TooManySegments error
Include the number of segments found in the request payload in
the TooManySegments error variant so the logged message shows
both the actual and maximum values.

Suggested-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-23 13:27:20 +00:00
Anatol Belski
edfbb7e180 block: Add DISCARD_WZ_MAX_PAYLOAD constant
Introduce DISCARD_WZ_MAX_PAYLOAD as the precomputed product of
DISCARD_WZ_SEG_SIZE and MAX_DISCARD_WRITE_ZEROES_SEG. Use it in the
DISCARD and WRITE_ZEROES segment count checks instead of repeating
the multiplication inline.

Suggested-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-23 13:27:20 +00:00
Julian Schindel
27a40ed32b ci: remove net_gen from allowed titles
On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
2026-03-23 12:50:38 +00:00
Julian Schindel
2b28c5b15a net_gen: replace net_gen with libc
The libc crate provides all functionality provided by the net_gen crate.
Removing the net_gen crate reduces the maintenance burden.

The switch to libc required some fixes, most notably the switch from a
`Vec<u8>` to a `CString` for the `net_util::Tap.if_name` field.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
2026-03-23 12:50:38 +00:00
Rob Bradford
3a56f20ee1 vmm: seccomp: Add readlink/readlinkat to vcpu filter
Now needed for IOMMU group resolution with VFIO.

Fixes: #7876

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-23 10:04:07 +00:00
Rob Bradford
e05065f509 build: Bump rust-vmm dependencies
Bump to the released versions that are compatible wherever possible but
for the vhost and vfio crates they are git hashes as no releases with
compatible versions have yet been made.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-23 10:04:07 +00:00
Rob Bradford
4f52e9355e virtio-devices: seccomp: Allow fcntl unconditionally
This now required after Rust-VMM crate bumps. Also reorder some syscalls
so that they are now in alphabetical order.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-23 10:04:07 +00:00
Anatol Belski
c79f3acfab block: Validate sector range for DISCARD and WRITE_ZEROES requests
Add range validation for DISCARD and WRITE_ZEROES, matching the
existing check in the read/write path. Per virtio spec section
5.2.6.1, a driver must not submit a request which would cause a
read or write beyond capacity. Use checked_add to guard against
overflow, then compare against disk_nsectors.

Without this, requests beyond device capacity pass through to the
host punch_hole/write_zeroes calls, relying on backend specific
behavior rather than returning VIRTIO_BLK_S_IOERR consistently.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-22 19:12:52 +00:00
Anatol Belski
d3cad420a5 block: Validate segment count for DISCARD and WRITE_ZEROES requests
Split the data length check into two conditions:

- reject descriptors shorter than one
virtio_blk_discard_write_zeroes segment, and
- reject payloads exceeding MAX_DISCARD_WRITE_ZEROES_SEG segments

Previously only the minimum length was checked and extra segments
were silently dropped.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-22 17:55:24 +00:00
Anatol Belski
fc79d08d7d block: qcow: Remove From<qcow::Error> for BlockError
All public qcow functions now return BlockResult with explicit error
classification at every site. The temporary From impl introduced in
the first commit of this series is no longer needed and is removed.

Internal functions in header.rs and the rebuild_refcounts helpers
stay on qcow::Result. Classification happens at the call site
boundary where qcow::Result meets BlockResult.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-20 22:21:24 +00:00
Anatol Belski
5410d4b2d5 block: qcow: Switch detect_image_type to BlockResult
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>
2026-03-20 22:21:24 +00:00
Anatol Belski
9daf1782a8 block: qcow: Switch rebuild_refcounts to BlockResult
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>
2026-03-20 22:21:24 +00:00
Anatol Belski
f6ec817b9b block: qcow: Switch resize and grow_l1_table to BlockResult
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>
2026-03-20 22:21:24 +00:00
Anatol Belski
524e620240 block: qcow: Classify errors in parse_qcow and BackingFile::new
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>
2026-03-20 22:21:24 +00:00
Anatol Belski
be9ef116aa block: qcow: Switch parse_qcow and BackingFile::new to BlockResult
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>
2026-03-20 22:21:24 +00:00
Anatol Belski
c4a5c7f843 block: qcow: Switch QcowFile constructors to BlockResult
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>
2026-03-20 22:21:24 +00:00
Anatol Belski
d77e3e7ca2 block: qcow: Add From<qcow::Error> for BlockError
Temporary From impl that classifies each qcow::Error variant into
the appropriate BlockErrorKind. This enables an incremental migration
of qcow functions from qcow::Result to BlockResult, where each
subsequent commit replaces bare ? sites with explicit BlockError::new
calls until this impl can be removed.

The mapping assigns InvalidFormat for structural header violations,
UnsupportedFeature for version and feature mismatches, CorruptImage
for internal inconsistencies, Overflow for nesting depth and Io for
everything else.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-20 22:21:24 +00:00
Muminul Islam
0c3249b14f tests: split integration helpers into common modules
Move shared integration test logic out of tests/integration.rs.
Add tests/common/{mod.rs,tests_wrappers.rs,utils.rs} and migrate
API, VM lifecycle, disk/net, and utility helpers.

Update integration.rs to import common modules and keep test
entrypoints thin.

Benefits:
 Reduces integration.rs size and duplication
 Groups reusable helpers by role
 Improves readability and future maintenance

Fixes: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7808

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-20 22:06:11 +00:00
Wei Liu
43642d8df8 tests: Make test_virtio_mmu more flexible
We only verify devices are under some group but not which one.

With the change, the acpi variable is only needed for aarch64. Add an
underscore prefix to avoid a compilation warning on x86_64.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-03-19 23:11:17 +00:00
Wei Liu
6e6127bbda tests: Make test_vdpa_block more flexible
We only care that the device exists under an IOMMU group but not which
one.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-03-19 23:11:17 +00:00
Wei Liu
5724a0189c tests: Make the IOMMU segment test case more flexible
Linux kernel's behavior changes overtime. The grouping can be different
across different versions and different architectures.

We only cares about the exact SBDF exists somewhere. It doesn't matter
which group it is under.

Change the check so that this test case is no longer tied to the
grouping behavior of a particular kernel.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-03-19 23:11:17 +00:00
Anatol Belski
0d062962ac block: qcow: Remove async_io::DiskFile impl from QcowDiskSync
QcowDiskSync now exclusively uses disk_file::DiskFile and
disk_file::AsyncDiskFile. The old async_io::DiskFile impl is removed
along with its unused imports (DiskFile, DiskFileError, DiskFileResult).

Tests are updated to import the new traits.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
b4dad66d35 virtio-devices: vmm: fuzz: Switch to DiskBackend
Change Block to hold DiskBackend instead of
Box<dyn async_io::DiskFile>. In device_manager, existing formats
(raw, vhd, vhdx) are wrapped in DiskBackend::Legacy while
QcowDiskSync uses DiskBackend::Next. The fuzz target is updated
accordingly.

The Error::DiskResize variant now carries BlockError instead of
DiskFileError, matching the BlockResult return type of
DiskBackend::resize().

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
264013b424 block: disk_file: Add DiskBackend dispatch enum
Introduce DiskBackend with two variants:
- Legacy: wraps Box<dyn async_io::DiskFile> for existing formats
- Next: wraps Box<dyn disk_file::AsyncFullDiskFile>

Methods return BlockResult, with DiskFileError converted up to
BlockError on the Legacy path. The Next path passes through
directly with zero conversion overhead.

This is a transitional type. Once all formats implement
AsyncFullDiskFile, DiskBackend and Legacy are removed and
callers hold Box<dyn AsyncFullDiskFile> directly.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
fe6e3e8fef block: qcow_sync: impl AsyncDiskFile for QcowDiskSync
Implement try_clone by sharing the metadata Arc and cloning the data
file descriptor. The new_async_io method creates a QcowSync worker
identical to the async_io::DiskFile version.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
1fc8e4adb6 block: qcow_sync: impl DiskFile for QcowDiskSync
Marker impl binding the DiskSize and HasTopology supertraits.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
f35bec19e8 block: qcow_sync: impl Resizable for QcowDiskSync
Add ErrorOp::Resize variant and implement the Resizable trait.
Resize is rejected when a backing file is present.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
554562fef2 block: qcow_sync: impl SparseCapable for QcowDiskSync
Advertise support for sparse operations and the zero flag. QCOW2
inherently supports both through cluster deallocation.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
63f5e6e97f block: qcow_sync: impl Geometry for QcowDiskSync
Uses the default DiskTopology (512B logical/physical) since
QCOW2 does not probe the underlying device geometry.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
b305a7670b block: qcow_sync: impl DiskFd for QcowDiskSync
Borrows the raw file descriptor from the underlying QcowRawFile
for fcntl() operations. Uses &self for shared access.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
4b731ee771 block: qcow_sync: impl PhysicalSize for QcowDiskSync
Delegate to QcowRawFile::physical_size() which returns the actual
host allocation size of the QCOW2 container file.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
0a7b6b089b block: qcow_sync: impl DiskSize for QcowDiskSync
Delegate to QcowMetadata::virtual_size() which returns the guest
visible capacity stored in the QCOW2 header.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
ad789024f9 block: qcow_sync: Add Debug impl for QcowDiskSync
The new composable traits require Debug. Implement it manually since
QcowMetadata contains RwLock state that cannot auto derive.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
dac8707c35 block: disk_file: Add AsyncFullDiskFile trait
Marker trait combining FullDiskFile and AsyncDiskFile. Blanket impl
covers any type implementing both supertraits.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
8623edb8aa block: disk_file: Add FullDiskFile trait
Marker trait bundling all optional capabilities (PhysicalSize, DiskFd,
SparseCapable, Resizable) on top of DiskFile. Blanket impl covers any
type implementing all constituent traits.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
50741cca2a block: disk_file: Add AsyncDiskFile trait
Extend DiskFile with async I/O construction for virtio queue workers.

AsyncDiskFile adds try_clone() for creating independent handles to
the same backing storage, and new_async_io() for constructing an
async I/O engine at the given ring depth.

Bounds: DiskFile + Unpin. Unpin ensures trait objects can be moved
freely (all concrete disk file types are naturally Unpin since they
hold no self referential state).

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
5d50d64671 block: disk_file: Add DiskFile supertrait
Bundles DiskSize and Geometry as the universal disk
capabilities every format must implement. Adds Sync so
that Arc<dyn DiskFile> can be shared across threads for
concurrent readonly access.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
da1b104493 block: disk_file: Add Resizable trait
Live disk resize support. Single method resize() taking
&mut self and the new size in bytes. Implementations may
return an error if the backend does not support resizing.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
a0a5718b42 block: disk_file: Add SparseCapable trait
Sparse and zero flag support for thin provisioned disk
images. Two methods with false defaults: sparse operations
(punch hole, write zeroes, discard) and zero flag
optimization in WRITE_ZEROES.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
b98a5cc58d block: disk_file: Add Geometry trait
Sector and cluster geometry of a disk image. Returns
DiskTopology with a default implementation providing
512B logical and physical block sizes. Formats that
probe the underlying device override this.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
2fa877e087 block: disk_file: Add DiskFd trait
Backing file descriptor access for disk images backed by
a file. Returns a BorrowedDiskFd that wraps the raw fd
with lifetime tracking. Not available for network or
memory backed disk formats.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
2df731b8d3 block: disk_file: Add PhysicalSize trait
Host allocation size for file-backed disk images. Reports
actual bytes occupied on the host filesystem. Not every
format supports this, e.g. network or memory backed disks.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
85e4e5027c block: disk_file: Add DiskSize trait
Reported capacity of a disk image. Every format, be it
file backed, network, memory, exposes a logical size.
Single method logical_size() returning the virtual size
in bytes.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Anatol Belski
a15bfcfee6 block: Add disk_file module skeleton
Composable disk capability traits with DiskFile as a supertrait
bundling DiskSize and Geometry. Optional capabilities are
separate traits: PhysicalSize, DiskFd, SparseCapable, Resizable.
AsyncDiskFile extends DiskFile with async I/O construction.

Empty module with doc comment, trait definitions follow.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 22:29:27 +00:00
Muminul Islam
297b683fcb tests: Add virtio block tests for confidential VMs
Add a full suite of test_virtio_block_* tests to the common_cvm
integration test module to verify virtio block functionality in
confidential guest environments.

The following tests are added, all using 4-vCPU confidential
VMs created via GuestFactory:
- test_virtio_block_io_uring (Raw image, io_uring backend)
- test_virtio_block_aio (Raw image, AIO backend)
- test_virtio_block_sync (Raw image, sync backend)
- test_virtio_block_qcow2 (QCOW2 image)
- test_virtio_block_qcow2_zlib (QCOW2 with zlib compression)
- test_virtio_block_qcow2_zstd (QCOW2 with zstd compression)
- test_virtio_block_qcow2_backing_zstd_file
- test_virtio_block_qcow2_backing_uncompressed_file
- test_virtio_block_qcow2_backing_raw_file

This extends CVM test coverage to all virtio block I/O backends
and disk image formats.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
ec730fde21 tests: Refactor _test_virtio_block and utilities to module scope
Move _test_virtio_block to module-level scope, accepting a
Guest reference instead of an image name string. Replace
hardcoded CPU, kernel, and cmdline arguments with default_cpus
and default_kernel_cmdline.

Promote all supporting disk utilities to module-level scope:
compute_backing_checksum, disk_check_consistency, run_qemu_img,
get_image_info, get_qcow2_v3_info, check_dirty_flag,
check_corrupt_flag, set_corrupt_flag, resolve_disk_path, and
compute_file_checksum.

Update all test_virtio_block_* call sites in common_parallel
to create guests via GuestFactory::new_regular_guest_factory()
with 4 vCPUs and pass them to the helper. This enables reuse
with different guest types such as confidential VMs.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
af4a14fa70 tests: Add direct kernel boot test for confidential VMs
Add test_direct_kernel_boot to the common_cvm integration test
module to verify that boot, CPU, memory, and MSI interrupt
functionality work correctly in confidential guest environments.

The test creates an Ubuntu Jammy-based confidential VM using
GuestFactory and delegates to the existing
_test_direct_kernel_boot helper.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
3b56ec240a tests: Refactor test_direct_kernel_boot into reusable helper
Extract direct kernel boot test logic into a standalone
_test_direct_kernel_boot helper that accepts a Guest reference.
The helper boots a VM, validates CPU count and memory using
generic validate_cpu_count and validate_memory methods, and
asserts 12 MSI interrupts in /proc/interrupts.

Replace hardcoded kernel and cmdline arguments with
default_kernel_cmdline(). Update the test call site in
common_parallel to use GuestFactory and delegate to the new
helper, enabling reuse with different guest types.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
38dc35cc33 tests: Add PCI multiple segments test for confidential VMs
Add test_pci_multiple_segments to the common_cvm integration
test module to verify multiple PCI segment support in
confidential guest environments.

The test uses 8 PCI segments, which exceeds the Linux default
of 6 and matches the maximum supported by the IGVM file for
SEV-SNP guests. A test disk is placed on segment 5 to validate
cross-segment device functionality.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
62bb7bfe45 tests: Refactor _test_pci_multiple_segments
Extract common PCI multiple segment disk test logic into
_test_pci_multiple_segments() and reuse it from the test case.

Switch guest creation to GuestFactory in test_pci_multiple_segments
and pass segment values through helper parameters.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
2aa92bf4b4 tests: Add platform argument support to kernel cmdline builder
Introduce default_kernel_cmdline_with_platform() in GuestCommand
that accepts an optional platform parameter. For confidential
VMs, the platform arg is prepended to sev_snp=on. For regular
VMs, it is passed via --platform if provided.

Retain default_kernel_cmdline() as a convenience wrapper that
calls the new method with None, preserving backward
compatibility.

This enables tests to pass additional platform configuration
such as num_pci_segments alongside the default kernel and
cmdline setup.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
6f8776ac50 tests: Add virtio net ctrl queue test for confidential VMs
Add test_virtio_net_ctrl_queue to the common_cvm integration
test module to verify that virtio net control queue functionality
works correctly in confidential guest environments.

The test creates an Ubuntu Jammy-based confidential VM using
GuestFactory and delegates to the existing
_test_virtio_net_ctrl_queue helper to validate MTU configuration
and ethtool offload settings.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
5378f8d614 tests: Refactor test_virtio_net_ctrl_queue into reusable helper
Extract virtio net control queue test logic into a standalone
_test_virtio_net_ctrl_queue helper that accepts a Guest
reference. The helper boots a VM with MTU 3000, verifies
ethtool can disable rx-gro-hw, and asserts the guest interface
MTU is correctly set.

Replace hardcoded kernel and cmdline arguments with
default_kernel_cmdline(). Update the test call site in
common_parallel to use GuestFactory and delegate to the new
helper, enabling reuse with different guest types.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
31f1e67be7 tests: Add PCI MSI interrupt test for confidential VMs
Add test_pci_msi to the common_cvm integration test module to
verify that PCI MSI interrupt functionality works correctly in
confidential guest environments.

The test creates an Ubuntu Jammy-based confidential VM using
GuestFactory and delegates to the existing _test_pci_msi helper
to validate MSI interrupts.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
acec5b00d6 tests: Refactor test_pci_msi into reusable helper
Extract PCI MSI interrupt test logic from test_pci_msi into a
standalone _test_pci_msi helper that accepts a Guest reference.
The helper boots a VM, waits for boot, and asserts that 12 MSI
interrupts are present in /proc/interrupts.

Replace hardcoded kernel and cmdline arguments with
default_kernel_cmdline(). Update the test call site in
common_parallel to use GuestFactory and delegate to the new
helper, enabling reuse with different guest types.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
bad5da622e tests: Add virtio queue affinity test for confidential VMs
Add test_virtio_queue_affinity to the common_cvm integration
test module to verify that per-queue CPU pinning works correctly
in confidential guest environments.

The test creates a 4-vCPU Ubuntu Jammy-based confidential VM
using GuestFactory and delegates to the existing
_test_virtio_queue_affinity helper to validate queue-to-core
affinity settings.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
c761d741bf tests: Refactor test_virtio_queue_affinity into reusable helper
Extract virtio queue affinity test logic into a standalone
_test_virtio_queue_affinity helper that accepts a Guest
reference. The helper verifies the host has at least 4 CPUs,
boots a VM with per-queue affinity on the cloud-init disk,
and asserts each disk queue thread is pinned to the expected
cores.

Replace hardcoded kernel and cmdline arguments with
default_cpus() and default_kernel_cmdline(). Update the
test call site in common_parallel to use GuestFactory and
delegate to the new helper, enabling reuse with different
guest types.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
7bf439975f tests: Add CPU affinity test for confidential VMs
Add test_cpu_affinity to the common_cvm integration test module
to verify that CPU pinning works correctly in confidential
guest environments.

The test creates a 2-vCPU Ubuntu Jammy-based confidential VM
using GuestFactory and delegates to the existing
_test_cpu_affinity helper to validate vCPU-to-core affinity.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
9059fb902d tests: Refactor test_cpu_affinity into reusable helper
Extract CPU affinity test logic from test_cpu_affinity into a
standalone _test_cpu_affinity helper that accepts a Guest
reference. The helper verifies the host has at least 4 CPUs,
boots a VM with affinity settings, and asserts vcpu0 is pinned
to cores 0,2 and vcpu1 to cores 1,3.

Add default_cpus_with_affinity_string() to Guest and
default_cpus_with_affinity() to GuestCommand in test_infra
to generate CPU arguments with affinity configuration.

Update the test_cpu_affinity call site in common_parallel to
use GuestFactory and delegate to the new helper, enabling
reuse with different guest types.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
848a280483 tests: Add multi-CPU test for confidential VMs
Add test_multi_cpu to the common_cvm integration test module
to verify that multi-CPU functionality works correctly in
confidential guest environments.

The test creates an Ubuntu Jammy-based confidential VM using
GuestFactory and delegates to the existing _test_multi_cpu
helper to validate SMP boot with multiple vCPUs.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
18ed8e61d2 tests: Refactor test_multi_cpu into reusable helper
Extract the multi-CPU test logic from the test_multi_cpu test
into a standalone _test_multi_cpu helper that accepts a Guest
reference as a parameter.

Update the test_multi_cpu call site in common_parallel to
create the guest via GuestFactory::new_regular_guest_factory()
and delegate to the new helper. This enables reuse of the test
logic with different guest types such as confidential VMs.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
c44b7679cf tests: Add virtio vsock test for confidential VMs
Add test_virtio_vsock to the common_cvm integration test module
to verify that virtio vsock functionality works correctly in
confidential guest environments.

The test creates an Ubuntu Jammy-based confidential VM using
GuestFactory and delegates to the existing _test_virtio_vsock
helper with hotplug disabled.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
dacc92a08f tests: Refactor _test_virtio_vsock to accept Guest parameter
Extract guest and kernel setup out of _test_virtio_vsock and
pass a Guest reference as a parameter instead. Replace explicit
kernel and cmdline arguments with default_kernel_cmdline().

Move guest creation to the test call sites using
GuestFactory::new_regular_guest_factory(), enabling reuse of
the helper with different guest types such as confidential VMs.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
b6d8df772d tests: Add power button test for confidential VMs
Add test_power_button to the common_cvm integration test module
to verify that power button functionality works correctly in
confidential guest environments.

The test creates an Ubuntu Jammy-based confidential VM using
GuestFactory and delegates to the existing _test_power_button
helper to validate the power button signal handling.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Muminul Islam
e278e5e931 tests: Refactor _test_power_button to accept Guest parameter
Remove the acpi bool parameter and internal guest and kernel
setup from _test_power_button. The function now accepts a Guest
reference and uses default_kernel_cmdline() instead of
hardcoded kernel paths.

Update test_power_button in common_parallel to create a regular
guest via GuestFactory. Update test_power_button_acpi in
aarch64_acpi to use with_kernel_path(edk2_path()) for ACPI
firmware support.

Add with_kernel_path() builder method on Guest in test_infra
to allow overriding the kernel path after guest creation.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-19 16:17:28 +00:00
Anatol Belski
19fa512f02 block: Batch drain AIO completions in next_completed_request
Collect up to 32 completions per io_getevents call instead of one
at a time, buffering them in the existing VecDeque. This reduces
syscalls from 128 to 4 per drain cycle at the default queue depth.
The stack cost is 1 KB per call.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 12:20:19 +00:00
Anatol Belski
0ee0441f2b virtio-devices: block: Derive discard alignment from topology
Set discard_sector_alignment from the logical block size
reported by the backend topology instead of hardcoding it
to 1 sector. This gives the guest accurate alignment hints
so it can avoid sub block discards that the filesystem
might silently ignore.

For example, on a 4K block filesystem the alignment is now
8 sectors (4096/512) instead of 1.

For image formats with their own allocation units (QCOW2
clusters, VHD/VHDX block sizes), the ideal alignment would
be derived from the format cluster/block size. This is
left for a followup that surfaces allocation granularity
through DiskTopology.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-19 01:07:49 +00:00
Anatol Belski
9655eaddd5 block: Use offset_of! for virtio_blk_discard_write_zeroes field offsets
Replace magic numeric offsets with mem::offset_of!() referencing the
virtio_blk_discard_write_zeroes struct from the virtio-bindings crate
when reading the sector, num_sectors and flags fields in the DISCARD
and WRITE_ZEROES request handlers.

No functional change.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-18 21:12:12 +00:00
Rob Bradford
54b27d8812 vmm: openapi: Add resume field to RestoreConfig
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-18 16:36:55 +00:00
Rob Bradford
068b5ecb63 vmm: Add support for resuming automatically on restore
Add an option that can be used when restoring to resume the VM. This is
particularly useful when restoring the VM via the direct VMM command
line, when you might not want/have an API socket configured.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-18 16:36:55 +00:00
Anatol Belski
f4772e7f4c ci: Exclude micro benchmarks from metrics CI
Skip micro_ prefixed tests in the metrics CI workflow to avoid
dashboard pollution. They can still be run on demand via
--test-filter micro_.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-18 09:43:06 +00:00
Anatol Belski
07479536ab scripts: Wire --test-exclude through test harness
Add --test-exclude to process_common_args in test-util.sh and forward
it to the performance-metrics binary from run_metrics.sh.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-18 09:43:06 +00:00
Anatol Belski
0e7b2d68fc performance-metrics: Add test exclude filter support
Add a --test-exclude flag that excludes tests matching the provided
keywords. Both --test-filter and --test-exclude are now applied before
--list-tests, so listing respects the active filters.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-18 09:43:06 +00:00
Anatol Belski
26035df8e5 tests: add integration test for WRITE_ZEROES with UNMAP flag
Add test_virtio_block_write_zeroes_unmap_raw to verify that the
VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP code path works correctly with
raw disk images.

The test creates a 128M raw disk and writes 64M of random data,
then uses fallocate --punch-hole on the guest block device, which
the Linux virtio-blk driver translates to VIRTIO_BLK_T_WRITE_ZEROES
with VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP set. It then verifies:

- the zeroed region reads back as zero from the guest
- the host file became sparse (punch_hole succeeded)
- FIEMAP confirms the file has holes

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-18 09:40:19 +00:00
Anatol Belski
3e2e453d89 block: Honor unmap flag in write zeroes requests
The write zeroes segment descriptor (struct
virtio_blk_discard_write_zeroes, virtio spec v1.2 section 5.2.6)
includes a flags field with an unmap bit. Per section 5.2.6.2, if
unmap is set, the device MAY deallocate the specified range of
sectors in the device backend storage, as if the discard command
had been sent.

Read the flags field and when the unmap bit is set, use punch_hole
to deallocate the range. Otherwise continue using write_zeroes via
ZERO_RANGE which preserves allocation.

This allows the guest to reclaim host disk space through write
zeroes requests on thin provisioned images.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-18 09:40:19 +00:00
Demi Marie Obenour
d609410b8b pci: Support injecting interrupts from externally-provided irqfds
The virtio vhost-user device backend prefers to use externally-provided
eventfds as irqfds.  This allows the frontend VM to notify the backend
VM directly, without the need for a userspace proxy process.  Since the
frontend can provide irqfds at any time, the backend needs to register
and unregister irqfds dynamically.

This is tricky because the functions that access the irqfd table all
take `&self`, not `&mut self`.  The obvious solution to this problem is
to wrap the table in a mutex.  Most of these functions are not called on
hot paths, but `.notifier()` is called whenever Cloud Hypervisor needs
to inject an interrupt into a guest.  Most devices don't need to
register irqfds at runtime, and for them, slowing down interrupt
injection would be wasteful.

Instead, require devices to opt-in to irqfd registration.  The irqfd
table now comes in two forms: one that contains a mutex and one that
does not.  The one containing a mutex can be mutated freely, while
attempting to mutate the one that does not will panic.

Right now, no code registeres irqfds at runtime, but this will change in
subsequent commits.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-03-18 09:25:27 +00:00
Demi Marie Obenour
9f62c33d00 vmm: Support external FDs for InterruptSourceGroup
This allows creating an InterruptSourceGroup with an externally provided
file descriptor.  It also allows changing the file descriptor
afterwards.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-03-18 09:25:27 +00:00
Demi Marie Obenour
e1c40211ae virtio-devices: Add set_notifier() method to VirtioInterrupt
It is currently left as unimplemented!().

No functional change intended as there are no callers.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-03-18 09:25:27 +00:00
Demi Marie Obenour
0f7dc514ba vmm: Wrap all InterruptRoute operations in a mutex
The InterruptRoute code tried to be thread-safe, but it wasn't.  In
particular, concurrently enabling and disabling an InterruptRoute could
result in the route thinking it was enabled (when it was disabled) or
visa versa.

Wrap all operations in a mutex and drop the attempt at being lock-free.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-03-18 09:25:27 +00:00
Anatol Belski
00957fa9db performance-metrics: Add AIO completion drain micro benchmark
Add micro_block_raw_aio_drain_128_us and
micro_block_raw_aio_drain_256_us tests that submit N AIO writes
to a temporary file, wait for the eventfd signal, then time how
long it takes to drain all completions via next_completed_request().

This measures per completion syscall overhead and provides a
baseline before any batching optimizations.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-17 22:12:50 +00:00
Anatol Belski
be6f63a740 performance-metrics: Add util module with shared micro benchmark helpers
These factor out common setup and synchronization patterns used by block
layer micro benchmarks.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-17 22:12:50 +00:00
Anatol Belski
b12620cf25 performance-metrics: Add num_ops field to PerformanceTestControl
Add an optional num_ops parameter for micro benchmarks to configure
workload size (e.g. number of AIO operations to submit). A warning
is emitted if it is accidentally set on a non micro test where it
has no effect.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-17 22:12:50 +00:00
Anatol Belski
c42cc478fe performance-metrics: Add micro benchmark support
Introduce support for in process micro benchmarks alongside the
existing VM level performance tests. Micro benchmarks are
integrated into the same PerformanceTest/TEST_LIST infrastructure
and follow the same iteration, timeout, and reporting pipeline.
They are distinguished by a micro_* name prefix.

The test dispatch loop is refactored to pre filter the test list
and gate init/cleanup behind a flag, so that pure micro benchmark
runs skip the expensive VM lifecycle entirely.  Mixed runs
(VM + micro) continue to work correctly.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-17 22:12:50 +00:00
Rob Bradford
ef91fc64e5 virtio-devices: vhost_user: Trigger interrupts in guest on resume
Trigger the interrupts in the guest for the virtio device queues behind
the vhost-user devices when resuming. This avoids a situation where
interrupts from the backend get lost when they are dispatched from the
backend when then guest is paused leading to the guest/backend
effectively waiting for each other to move forward. This is more
reproducible with longer durations between pause and resume as there is
more opportunity for the backend to completely process it's queue and
fire all the interrupts.

It's perfectly safe and allowed by the virtio spec to generate these
interrupts and the performance impact is negligible and is a safe way to
ensure forward progress after a resume.

See: #7850

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-17 08:44:39 +00:00
dependabot[bot]
ad3179fe11 build: Bump the non-rust-vmm group across 2 directories with 23 updates
Bumps the non-rust-vmm group with 15 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_with](https://github.com/jonasbb/serde_with) | `3.17.0` | `3.18.0` |
| [clap](https://github.com/clap-rs/clap) | `4.5.60` | `4.6.0` |
| [num_enum](https://github.com/illicitonion/num_enum) | `0.7.5` | `0.7.6` |
| [gdbstub](https://github.com/daniel5151/gdbstub) | `0.7.9` | `0.7.10` |
| [gdbstub_arch](https://github.com/daniel5151/gdbstub) | `0.3.2` | `0.3.3` |
| [anstyle](https://github.com/rust-cli/anstyle) | `1.0.13` | `1.0.14` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.56` | `1.2.57` |
| [clap_lex](https://github.com/clap-rs/clap) | `1.0.0` | `1.1.0` |
| [colorchoice](https://github.com/rust-cli/anstyle) | `1.0.4` | `1.0.5` |
| [libz-sys](https://github.com/rust-lang/libz-sys) | `1.1.24` | `1.1.25` |
| [once_cell](https://github.com/matklad/once_cell) | `1.21.3` | `1.21.4` |
| [openssl-sys](https://github.com/rust-openssl/rust-openssl) | `0.9.111` | `0.9.112` |
| [portable-atomic-util](https://github.com/taiki-e/portable-atomic-util) | `0.2.5` | `0.2.6` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.26.0` | `3.27.0` |
| [uds_windows](https://github.com/haraldh/rust_uds_windows) | `1.2.0` | `1.2.1` |

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

| Package | From | To |
| --- | --- | --- |
| [serde_with](https://github.com/jonasbb/serde_with) | `3.17.0` | `3.18.0` |
| [clap](https://github.com/clap-rs/clap) | `4.5.60` | `4.6.0` |
| [num_enum](https://github.com/illicitonion/num_enum) | `0.7.5` | `0.7.6` |
| [gdbstub](https://github.com/daniel5151/gdbstub) | `0.7.9` | `0.7.10` |
| [gdbstub_arch](https://github.com/daniel5151/gdbstub) | `0.3.2` | `0.3.3` |
| [anstyle](https://github.com/rust-cli/anstyle) | `1.0.13` | `1.0.14` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.56` | `1.2.57` |
| [clap_lex](https://github.com/clap-rs/clap) | `1.0.0` | `1.1.0` |
| [colorchoice](https://github.com/rust-cli/anstyle) | `1.0.4` | `1.0.5` |
| [once_cell](https://github.com/matklad/once_cell) | `1.21.3` | `1.21.4` |



Updates `serde_with` from 3.17.0 to 3.18.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.17.0...v3.18.0)

Updates `clap` from 4.5.60 to 4.6.0
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.60...clap_complete-v4.6.0)

Updates `num_enum` from 0.7.5 to 0.7.6
- [Commits](https://github.com/illicitonion/num_enum/compare/0.7.5...0.7.6)

Updates `gdbstub` from 0.7.9 to 0.7.10
- [Release notes](https://github.com/daniel5151/gdbstub/releases)
- [Changelog](https://github.com/daniel5151/gdbstub/blob/master/CHANGELOG.md)
- [Commits](https://github.com/daniel5151/gdbstub/compare/0.7.9...0.7.10)

Updates `gdbstub_arch` from 0.3.2 to 0.3.3
- [Release notes](https://github.com/daniel5151/gdbstub/releases)
- [Changelog](https://github.com/daniel5151/gdbstub/blob/master/CHANGELOG.md)
- [Commits](https://github.com/daniel5151/gdbstub/commits)

Updates `anstyle` from 1.0.13 to 1.0.14
- [Commits](https://github.com/rust-cli/anstyle/compare/v1.0.13...v1.0.14)

Updates `cc` from 1.2.56 to 1.2.57
- [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.56...cc-v1.2.57)

Updates `clap_builder` from 4.5.60 to 4.6.0
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.60...v4.6.0)

Updates `clap_lex` from 1.0.0 to 1.1.0
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_lex-v1.0.0...clap_lex-v1.1.0)

Updates `colorchoice` from 1.0.4 to 1.0.5
- [Commits](https://github.com/rust-cli/anstyle/compare/colorchoice-v1.0.4...colorchoice-v1.0.5)

Updates `darling` from 0.21.3 to 0.23.0
- [Release notes](https://github.com/TedDriggs/darling/releases)
- [Changelog](https://github.com/TedDriggs/darling/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TedDriggs/darling/compare/v0.21.3...v0.23.0)

Updates `darling_core` from 0.21.3 to 0.23.0
- [Release notes](https://github.com/TedDriggs/darling/releases)
- [Changelog](https://github.com/TedDriggs/darling/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TedDriggs/darling/compare/v0.21.3...v0.23.0)

Updates `darling_macro` from 0.21.3 to 0.23.0
- [Release notes](https://github.com/TedDriggs/darling/releases)
- [Changelog](https://github.com/TedDriggs/darling/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TedDriggs/darling/compare/v0.21.3...v0.23.0)

Updates `libz-sys` from 1.1.24 to 1.1.25
- [Release notes](https://github.com/rust-lang/libz-sys/releases)
- [Commits](https://github.com/rust-lang/libz-sys/compare/1.1.24...1.1.25)

Updates `num_enum_derive` from 0.7.5 to 0.7.6
- [Commits](https://github.com/illicitonion/num_enum/compare/0.7.5...0.7.6)

Updates `once_cell` from 1.21.3 to 1.21.4
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.3...v1.21.4)

Updates `openssl-sys` from 0.9.111 to 0.9.112
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-sys-v0.9.111...openssl-sys-v0.9.112)

Updates `portable-atomic-util` from 0.2.5 to 0.2.6
- [Release notes](https://github.com/taiki-e/portable-atomic-util/releases)
- [Changelog](https://github.com/taiki-e/portable-atomic-util/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/portable-atomic-util/compare/v0.2.5...v0.2.6)

Updates `serde_with_macros` from 3.17.0 to 3.18.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.17.0...v3.18.0)

Updates `tempfile` from 3.26.0 to 3.27.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.26.0...v3.27.0)

Updates `uds_windows` from 1.2.0 to 1.2.1
- [Release notes](https://github.com/haraldh/rust_uds_windows/releases)
- [Changelog](https://github.com/haraldh/rust_uds_windows/blob/master/CHANGELOG.md)
- [Commits](https://github.com/haraldh/rust_uds_windows/compare/v1.2.0...v1.2.1)

Updates `serde_with` from 3.17.0 to 3.18.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.17.0...v3.18.0)

Updates `clap` from 4.5.60 to 4.6.0
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.60...clap_complete-v4.6.0)

Updates `num_enum` from 0.7.5 to 0.7.6
- [Commits](https://github.com/illicitonion/num_enum/compare/0.7.5...0.7.6)

Updates `gdbstub` from 0.7.9 to 0.7.10
- [Release notes](https://github.com/daniel5151/gdbstub/releases)
- [Changelog](https://github.com/daniel5151/gdbstub/blob/master/CHANGELOG.md)
- [Commits](https://github.com/daniel5151/gdbstub/compare/0.7.9...0.7.10)

Updates `gdbstub_arch` from 0.3.2 to 0.3.3
- [Release notes](https://github.com/daniel5151/gdbstub/releases)
- [Changelog](https://github.com/daniel5151/gdbstub/blob/master/CHANGELOG.md)
- [Commits](https://github.com/daniel5151/gdbstub/commits)

Updates `anstream` from 0.6.21 to 1.0.0
- [Commits](https://github.com/rust-cli/anstyle/compare/anstream-v0.6.21...anstream-v1.0.0)

Updates `anstyle` from 1.0.13 to 1.0.14
- [Commits](https://github.com/rust-cli/anstyle/compare/v1.0.13...v1.0.14)

Updates `anstyle-parse` from 0.2.7 to 1.0.0
- [Commits](https://github.com/rust-cli/anstyle/compare/anstyle-parse-v0.2.7...anstyle-parse-v1.0.0)

Updates `cc` from 1.2.56 to 1.2.57
- [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.56...cc-v1.2.57)

Updates `clap_builder` from 4.5.60 to 4.6.0
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.60...v4.6.0)

Updates `clap_lex` from 1.0.0 to 1.1.0
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_lex-v1.0.0...clap_lex-v1.1.0)

Updates `colorchoice` from 1.0.4 to 1.0.5
- [Commits](https://github.com/rust-cli/anstyle/compare/colorchoice-v1.0.4...colorchoice-v1.0.5)

Updates `darling` from 0.21.3 to 0.23.0
- [Release notes](https://github.com/TedDriggs/darling/releases)
- [Changelog](https://github.com/TedDriggs/darling/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TedDriggs/darling/compare/v0.21.3...v0.23.0)

Updates `darling_core` from 0.21.3 to 0.23.0
- [Release notes](https://github.com/TedDriggs/darling/releases)
- [Changelog](https://github.com/TedDriggs/darling/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TedDriggs/darling/compare/v0.21.3...v0.23.0)

Updates `darling_macro` from 0.21.3 to 0.23.0
- [Release notes](https://github.com/TedDriggs/darling/releases)
- [Changelog](https://github.com/TedDriggs/darling/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TedDriggs/darling/compare/v0.21.3...v0.23.0)

Updates `num_enum_derive` from 0.7.5 to 0.7.6
- [Commits](https://github.com/illicitonion/num_enum/compare/0.7.5...0.7.6)

Updates `once_cell` from 1.21.3 to 1.21.4
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.3...v1.21.4)

Updates `serde_with_macros` from 3.17.0 to 3.18.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.17.0...v3.18.0)

---
updated-dependencies:
- dependency-name: serde_with
  dependency-version: 3.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: num_enum
  dependency-version: 0.7.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: gdbstub
  dependency-version: 0.7.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: gdbstub_arch
  dependency-version: 0.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: anstyle
  dependency-version: 1.0.14
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.57
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.6.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: clap_lex
  dependency-version: 1.1.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: colorchoice
  dependency-version: 1.0.5
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: darling
  dependency-version: 0.23.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: darling_core
  dependency-version: 0.23.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: darling_macro
  dependency-version: 0.23.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: libz-sys
  dependency-version: 1.1.25
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: num_enum_derive
  dependency-version: 0.7.6
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: once_cell
  dependency-version: 1.21.4
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: openssl-sys
  dependency-version: 0.9.112
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: portable-atomic-util
  dependency-version: 0.2.6
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with_macros
  dependency-version: 3.18.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: tempfile
  dependency-version: 3.27.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: uds_windows
  dependency-version: 1.2.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with
  dependency-version: 3.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: num_enum
  dependency-version: 0.7.6
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: gdbstub
  dependency-version: 0.7.10
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: gdbstub_arch
  dependency-version: 0.3.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: anstream
  dependency-version: 1.0.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: anstyle
  dependency-version: 1.0.14
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: anstyle-parse
  dependency-version: 1.0.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.57
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.6.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: clap_lex
  dependency-version: 1.1.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: colorchoice
  dependency-version: 1.0.5
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: darling
  dependency-version: 0.23.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: darling_core
  dependency-version: 0.23.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: darling_macro
  dependency-version: 0.23.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: num_enum_derive
  dependency-version: 0.7.6
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: once_cell
  dependency-version: 1.21.4
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with_macros
  dependency-version: 3.18.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 01:08:54 +00:00
Anatol Belski
083892b2e2 tests: windows: remove sparse=off workaround
The config space fix in the previous commit correctly populates
the discard and write zeroes fields, so the sparse=off
workaround is no longer needed for Windows guests.

Replace default_disks_sparse_off() with default_disks() in all
Windows test cases and remove the explicit sparse=off from the
multi queue test.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-16 22:30:23 +00:00
Anatol Belski
a7fefb63dd virtio-devices: block: Populate discard and write zeroes config
When VIRTIO_BLK_F_DISCARD or VIRTIO_BLK_F_WRITE_ZEROES features
are advertised, the virtio spec v1.2, sections 5.2.4 and
5.2.6.1, requires the corresponding VirtioBlockConfig fields
to contain valid, non zero values. Leaving them at zero causes
strictly behaved drivers to either reject the features or crash.

Populate max_discard_sectors, max_discard_seg,
discard_sector_alignment, max_write_zeroes_sectors,
max_write_zeroes_seg and write_zeroes_may_unmap after
feature advertisement so drivers can safely negotiate
these features.

Fixes: #7849

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-16 22:30:23 +00:00
Alyssa Ross
1539b195d1 virtio-devices: remove incorrect comment
This device is not called virtio-vhost-user; that's something else.

I don't think the comment really clarifies anything anyway, so just
remove it.

Fixes: 8c618ff5e ("virtio-devices: generic-vhost-user: implement device")
Signed-off-by: Alyssa Ross <hi@alyssa.is>
2026-03-16 22:27:00 +00:00
Souradeep
f1875668e0 scripts: Rename sha1sums-aarch64 to sha1sums-aarch64-common
Rename the aarch64 sha1sums file to sha1sums-aarch64-common to follow
the same naming convention as sha1sums-x86_64-common. This allows
run_metrics.sh to use the generic sha1sums-${TEST_ARCH}-common
pattern for all architectures, removing the need for aarch64-specific
conditionals.

Update run_integration_tests_aarch64.sh to reference the renamed file.

Signed-off-by: Souradeep <schakrabarti@microsoft.com>
2026-03-16 12:35:37 +00:00
Rob Bradford
0da63a4507 tests: Ensure clippy --tests runs on integration.rs
Include the file when running clippy as well as when building with
devcli_testenv set.

Fixes: #7846

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-16 10:54:05 +00:00
Rob Bradford
14d8cf5f1e tests: Fix clippy issue related to passing by value
warning: this argument is passed by value, but not consumed in the function body
    --> cloud-hypervisor/tests/integration.rs:3785:51
     |
3785 |     fn run_multiqueue_qcow2_test<F>(image_config: QcowTestImageConfig, test_fn: F)
     |                                                   ^^^^^^^^^^^^^^^^^^^
     |
help: or consider marking this type as `Copy`
    --> cloud-hypervisor/tests/integration.rs:3774:5
     |
3774 |     enum QcowTestImageConfig {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
     = note: requested on the command line with `-D clippy::needless-pass-by-value`

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-16 10:54:05 +00:00
Rob Bradford
0a7e32c312 tests: Fix clippy issue related to unnecessary use of .to_string()
error: unnecessary use of `to_string`
    --> cloud-hypervisor/tests/integration.rs:6765:38
     |
6765 |             exec_host_command_output(&"sudo dd if=/dev/zero of=/tmp/resize.img bs=1M count=16".to_string())
     |                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `"sudo dd if=/dev/zero of=/tmp/resize.img bs=1M count=16"`
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned
     = note: `-D clippy::unnecessary-to-owned` implied by `-D clippy::all`
     = help: to override `-D clippy::all` add `#[allow(clippy::unnecessary_to_owned)]`

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-16 10:54:05 +00:00
Rob Bradford
93d8896042 tests: Fix clippy issue related to unnecessary move
error: the borrowed expression implements the required traits
    --> cloud-hypervisor/tests/integration.rs:8510:32
     |
8510 |         disk_check_consistency(&test_disk_path, None);
     |                                ^^^^^^^^^^^^^^^ help: change this to: `test_disk_path`
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrows_for_generic_args
     = note: `-D clippy::needless-borrows-for-generic-args` implied by `-D clippy::all`
     = help: to override `-D clippy::all` add `#[allow(clippy::needless_borrows_for_generic_args)]`

error: could not compile `cloud-hypervisor` (test "integration") due to 7 previous errors

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-16 10:54:05 +00:00
Rob Bradford
41a2a8ea62 tests: Fix clippy issue related to unnecessary enumerate()
error: you seem to use `.enumerate()` and immediately discard the index
    --> cloud-hypervisor/tests/integration.rs:7675:72
     |
7675 |                 for (_i, (offset, length)) in discard_operations.iter().enumerate() {
     |                                                                        ^^^^^^^^^^^^
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unused_enumerate_index
     = note: `-D clippy::unused-enumerate-index` implied by `-D clippy::all`
     = help: to override `-D clippy::all` add `#[allow(clippy::unused_enumerate_index)]`
help: remove the `.enumerate()` call
     |
7675 -                 for (_i, (offset, length)) in discard_operations.iter().enumerate() {
7675 +                 for (offset, length) in discard_operations.iter() {
     |

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-16 10:54:05 +00:00
Rob Bradford
b92ab6e4b1 tests: Fix clippy issue related to unnecessary closure
error: unnecessary closure used with `bool::then`
    --> cloud-hypervisor/tests/integration.rs:3488:9
     |
3488 |         output.status.success().then(|| ())?;
     |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
     = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D clippy::all`
     = help: to override `-D clippy::all` add `#[allow(clippy::unnecessary_lazy_evaluations)]`
help: use `then_some` instead
     |
3488 -         output.status.success().then(|| ())?;
3488 +         output.status.success().then_some(())?;
     |

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-16 10:54:05 +00:00
Rob Bradford
c14fd5c575 tests: Fix clippy issue related to formatting
error: consider adding a `;` to the last statement for consistent formatting
    --> cloud-hypervisor/tests/integration.rs:2516:9
     |
2516 |         _test_simple_launch(&guest)
     |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add a `;` here: `_test_simple_launch(&guest);`
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-16 10:54:05 +00:00
Rob Bradford
5b23f0a154 tests: Fix clippy issues related to format string use
error: variables can be used directly in the `format!` string
     --> cloud-hypervisor/tests/integration.rs:12770:27
      |
12770 |         let driver_path = format!("{}/driver", NVIDIA_VFIO_DEVICE);
      |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args
help: change this to
      |
12770 -         let driver_path = format!("{}/driver", NVIDIA_VFIO_DEVICE);
12770 +         let driver_path = format!("{NVIDIA_VFIO_DEVICE}/driver");

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-16 10:54:05 +00:00
Rob Bradford
a97348d24e net_util: Fix clippy errors related to use of String
error: this argument is passed by value, but not consumed in the function body
   --> net_util/src/tap.rs:685:17
    |
685 |         ifname: String,
    |                 ^^^^^^ help: consider changing the type to: `&str`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
    = note: requested on the command line with `-D clippy::needless-pass-by-value`

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-16 10:54:05 +00:00
Philipp Schuster
faebd57db8 docs: Add git commit hygiene guidelines to CONTRIBUTING.md
Write down our policy for git commit hygiene, especially when it comes
to the history, i.e., multiple git commits in a PR.

TL;DR: Commits must be revieable units guiding reviewers how the
       developer got from A to B.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-16 10:12:50 +00:00
Anatol Belski
1733e08a0f block: raw: Replace FALLOC_FL_* consts with libc::* in raw backends
raw_sync, raw_async, and raw_async_aio each defined
FALLOC_FL_PUNCH_HOLE, FALLOC_FL_KEEP_SIZE, and FALLOC_FL_ZERO_RANGE as
local constants in their punch_hole() and write_zeroes()
implementations. These are available from the libc crate directly.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-14 14:00:29 +00:00
Anatol Belski
05aa9ebcd4 block: Replace local FALLOC_FL_* constants with libc::* in probe
probe_file_sparse_support() defined FALLOC_FL_KEEP_SIZE,
FALLOC_FL_PUNCH_HOLE, and FALLOC_FL_ZERO_RANGE as local constants.
These are available from the libc crate directly.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-14 14:00:29 +00:00
Anatol Belski
d883b54fb7 block: Deduplicate raw_sync and raw_async_aio tests
Replace duplicated test bodies with thin wrappers that construct
the backend-specific AsyncIo instance and delegate to the shared
raw_async_io_tests helpers.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-14 13:39:41 +00:00
Anatol Belski
5aa68ddf9e block: Add raw_async_io_tests shared test helpers
Add raw_async_io_tests.rs with punch_hole, write_zeroes, and
multiple_operations helpers that take &mut dyn AsyncIo + &mut File.
These are raw-backend-specific. They verify data by reading the
underlying file directly, which only works for plain file backends
without container format metadata.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-14 13:39:41 +00:00
Muminul Islam
c53bc3d170 scripts: refactor prepare_vdpa for distro-specific setup
Split the vDPA preparation flow into helper functions for
building modules, validating availability, loading modules,
and creating devices.

Build the vdpa_sim modules only on Ubuntu, where the script
installs dependencies and compiles them from the matching
kernel source. On other distributions, reuse the installed
kernel modules and verify that they are available before
continuing.

This makes the script easier to follow and supports systems
such as Azure Linux, where the modules are provided by the
kernel package.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-14 09:46:38 +00:00
Peter Oskolkov
8b60b38281 virtio-devices: block: handle corrupted requests with NEEDS_RESET
Signed-off-by: Peter Oskolkov <posk@google.com>
2026-03-14 00:21:02 +00:00
Peter Oskolkov
563303b50a virtio-devices: net: handle corrupted requests with NEEDS_RESET
A buggy or malicious guest may write an inappropriate value into
virtqueue's next_avail field. This will result in an error
when iterating over the queue:

863837ef86/virtio-queue/src/queue.rs (L708)

but this error is (logged and) ignored if pop_descriptor_chain()
is used:

863837ef86/virtio-queue/src/queue.rs (L583)

A reasonable approach, implemented here, is to mark the device as
NEEDS_RESET and ignore further queue events until the guest
reinitializes the device.

How this patch was tested:

Linux kernel was patched to trigger a bad next_avail when the
virtqueue queue counter reaches 5000:

--------------- START OF LINUX KERNEL PATCH ----------
$ git diff
diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c
index b784aab668670..989f2a0c64a77 100644
--- a/drivers/virtio/virtio_ring.c
+++ b/drivers/virtio/virtio_ring.c
@@ -15,6 +15,9 @@
 #include <linux/spinlock.h>
 #include <xen/xen.h>

+
+void virtqueue_kick_always(struct virtqueue *vq);
+
 #ifdef DEBUG
 /* For development, we want to crash whenever the ring is screwed. */
 #define BAD_RING(_vq, fmt, args...)                            \
@@ -677,6 +680,12 @@ static inline int virtqueue_add_split(
                   struct virtqueue *_vq,
         * new available array entries. */
        virtio_wmb(vq->weak_barriers);
        vq->split.avail_idx_shadow++;
+       {
+        if ((vq->split.avail_idx_shadow % 100) == 0)
+            printk(KERN_ERR "avail idx: %d",
+                  (int)vq->split.avail_idx_shadow);
+               if (vq->split.avail_idx_shadow == 5000)
+               vq->split.avail_idx_shadow = 0;
+       }
        vq->split.vring.avail->idx = cpu_to_virtio16(_vq->vdev,
                                      vq->split.avail_idx_shadow);
        vq->num_added++;
@@ -689,6 +698,11 @@ static inline int virtqueue_add_split(
                  struct virtqueue *_vq,
        if (unlikely(vq->num_added == (1 << 16) - 1))
                virtqueue_kick(_vq);

+       {
+               if (unlikely(vq->split.avail_idx_shadow == 0))
+                       virtqueue_kick_always(_vq);
+       }
+
        return 0;

 unmap_release:
@@ -2515,6 +2529,11 @@ bool virtqueue_kick(struct virtqueue *vq)
 }
 EXPORT_SYMBOL_GPL(virtqueue_kick);

+void virtqueue_kick_always(struct virtqueue *vq)
+{
+       virtqueue_kick_prepare(vq);
+       virtqueue_notify(vq);
+}
 /**
  * virtqueue_get_buf_ctx - get the next used buffer
  * @_vq: the struct virtqueue we're talking about.
--------------- END OF LINUX KERNEL PATCH ----------

Then the kernel was booted, and the host pinged until the
nic became unresponsive:

ping -i 0.002 192.168.4.1

Device status was confirmed using

cat /sys/class/net/eth0/device/status

(it was 0x4f).

Then the device was re-initialized:

DEV_NAME=$(basename $(readlink -f /sys/class/net/eth0/device))
echo $DEV_NAME | tee /sys/bus/virtio/drivers/virtio_net/unbind
echo $DEV_NAME | tee /sys/bus/virtio/drivers/virtio_net/bind
ip link set eth0 up

At this point networking became healthly again.

Signed-off-by: Peter Oskolkov <posk@google.com>
2026-03-14 00:21:02 +00:00
Peter Oskolkov
b5053ae4de virtio-devices: wire driver_status to EpollHandler
Signed-off-by: Peter Oskolkov <posk@google.com>
2026-03-14 00:21:02 +00:00
Peter Oskolkov
21bd3ae916 virtio-devices: switch driver_status to Arc<AtomicU8>
Signed-off-by: Peter Oskolkov <posk@google.com>
2026-03-14 00:21:02 +00:00
Peter Oskolkov
f77c6ef78b virtio-devices: introduce ActivationContext for device activation
Signed-off-by: Peter Oskolkov <posk@google.com>
2026-03-14 00:21:02 +00:00
Rob Bradford
ab8169c855 build: Bump timeout on integration tests
We now have more tests and are hitting up against the timeout

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-13 21:17:21 +00:00
Shayon Mukherjee
ec389c4fae tests: add integration tests for on-demand snapshot restore
Add UFFD restore tests to common_sequential: basic anonymous RAM,
shared memory, and hugepage-backed zone memory. Each exercises the
full snapshot/restore cycle with memory_restore_mode=ondemand and
verifies CPU count, memory size, and device health after resume.

Signed-off-by: Shayon Mukherjee <shayonj@gmail.com>
2026-03-13 21:17:21 +00:00
Shayon Mukherjee
c417924a29 vmm: memory_manager: add on-demand snapshot restore via userfaultfd
When memory_restore_mode=ondemand is specified on the restore command,
the memory manager creates a userfaultfd descriptor, registers each
guest RAM range for missing-page fault interception, and spawns a
handler thread that serves page faults from the snapshot file using
UFFDIO_COPY. This avoids reading the entire memory-ranges file into
guest RAM before restore completes.

The handler uses epoll to multiplex the userfaultfd and a stop eventfd
for clean shutdown. Concurrent faults from multiple vCPUs are handled
by treating EEXIST as a benign race and waking blocked threads with
UFFDIO_WAKE. Once all pages have been served the handler exits
automatically. If the handler thread panics the VMM is signalled to
exit since the VM cannot continue without page fault service.

MemoryZone gains a backing_page_size field so the handler resolves
fault granularity from the zone rather than the top-level config.

Errors from the UFFD setup path use a structured UffdError enum
and a new MigratableError::OnDemandRestore variant, with a From
impl to keep call sites concise.

The seccomp filter is updated to allow the userfaultfd syscall and
the four uffd ioctls (UFFDIO_API, UFFDIO_COPY, UFFDIO_REGISTER,
UFFDIO_WAKE) under the VMM thread profile.

Signed-off-by: Shayon Mukherjee <shayonj@gmail.com>
2026-03-13 21:17:21 +00:00
Shayon Mukherjee
bf85af907e vmm: config: add memory_restore_mode to RestoreConfig
Add a MemoryRestoreMode enum (Copy | OnDemand) to RestoreConfig so
the restore path can be selected at restore time. Copy preserves the
existing eager read-copy behavior. OnDemand enables userfaultfd-based
demand paging and fails restore if the kernel does not support it.

Validate that prefault=on is not combined with OnDemand mode.

Update the OpenAPI spec with the new enum field.

Signed-off-by: Shayon Mukherjee <shayonj@gmail.com>
2026-03-13 21:17:21 +00:00
Shayon Mukherjee
8340307ace vmm: add uffd abstraction module
Add safe Rust wrappers around the raw userfaultfd ioctls: create
(syscall + API handshake), register (missing-page mode), copy
(resolve fault), and wake (unblock threads after EEXIST race).

These are used by the demand-paged snapshot restore handler in a
subsequent commit.

Signed-off-by: Shayon Mukherjee <shayonj@gmail.com>
2026-03-13 21:17:21 +00:00
Shayon Mukherjee
de37f27945 vmm: add userfaultfd constants module
Add a small constants module with the ioctl numbers and protocol
constants needed for userfaultfd-based demand-paged snapshot restore.
These are derived from the kernel's include/uapi/linux/userfaultfd.h.

Signed-off-by: Shayon Mukherjee <shayonj@gmail.com>
2026-03-13 21:17:21 +00:00
Philipp Schuster
fcdb10373b vmm: migration: emit event for each memory iteration
Emit a "vm.migration-memory-iteration" event after every precopy memory
iteration to allow management software to observe forward progress
during migration.

This event is primarily intended for integration with management
software such as libvirt, where it maps to
VIR_DOMAIN_EVENT_ID_MIGRATION_ITERATION.

The event is intentionally independent of any upcoming migration
metrics endpoint. Detailed migration statistics will be exposed via
that endpoint, while this event provides a lightweight progress signal
expected by external management layers.

With this event, management software can detect forward progress during
migration without being blocked on any upcoming migration metrics
endpoint.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 13:18:07 +00:00
Philipp Schuster
39768704f3 vmm: migration: add iteration metrics and downtime estimation
Add infrastructure to collect metrics during precopy memory migration
iterations.

For each iteration we now track transferred bytes, duration, bandwidth,
and estimate the expected downtime based on the remaining memory of the
current iteration and measured bandwidth. These metrics are logged and
used to decide when to stop the precopy phase.

This also introduces basic termination conditions such as:
  - maximum number of iterations
  - reaching a target downtime
  - maximum migration duration

This is the fundament for an upcoming API call to publicly export
statistics about an ongoing live migration. The changes are, however,
self-contained and helpful by themselves.

The new log now looks somewhat as in the following, providing lots of
helpful insights (especially the bandwidth and estimated downtime are
helpful). The metrics were measured with CHV build with `--release`, a
VM under heavy load (lots of memory writes), same-host TCP
migration and prefault=on:

```
cloud-hypervisor:  12.702682s: <vmm> DEBUG:vmm/src/lib.rs:1313 -- Precopy: iter=0 total=6144MiB curr=6144MiB bw=1986.83MiB/s transfer=3.09s overhead=0ms est_downtime=0ms elapsed=3.11s avg_bw=1975.41MiB/s
cloud-hypervisor:  15.728419s: <vmm> DEBUG:vmm/src/lib.rs:1313 -- Precopy: iter=1 total=11562MiB curr=5418MiB bw=1824.44MiB/s transfer=2.97s overhead=56ms est_downtime=2726ms elapsed=6.14s avg_bw=1884.21MiB/s
cloud-hypervisor:  18.710428s: <vmm> DEBUG:vmm/src/lib.rs:1313 -- Precopy: iter=2 total=16980MiB curr=5418MiB bw=1854.25MiB/s transfer=2.92s overhead=59ms est_downtime=2969ms elapsed=9.12s avg_bw=1862.17MiB/s
cloud-hypervisor:  21.783699s: <vmm> DEBUG:vmm/src/lib.rs:1313 -- Precopy: iter=3 total=22407MiB curr=5428MiB bw=1799.43MiB/s transfer=3.02s overhead=56ms est_downtime=2926ms elapsed=12.19s avg_bw=1837.92MiB/s
cloud-hypervisor:  25.785696s: <vmm> DEBUG:vmm/src/lib.rs:1313 -- Precopy: iter=4 total=27825MiB curr=5418MiB bw=1375.53MiB/s transfer=3.94s overhead=62ms est_downtime=3010ms elapsed=16.19s avg_bw=1718.26MiB/s
cloud-hypervisor:  29.000349s: <vmm> DEBUG:vmm/src/lib.rs:1313 -- Precopy: iter=5 total=33243MiB curr=5418MiB bw=1727.60MiB/s transfer=3.14s overhead=78ms est_downtime=3938ms elapsed=19.41s avg_bw=1712.82MiB/s
cloud-hypervisor:  32.215805s: <vmm> DEBUG:vmm/src/lib.rs:1313 -- Precopy: iter=6 total=38671MiB curr=5429MiB bw=1724.03MiB/s transfer=3.15s overhead=66ms est_downtime=3142ms elapsed=22.62s avg_bw=1709.33MiB/s
cloud-hypervisor:  32.275215s: <vmm> DEBUG:vmm/src/lib.rs:1286 -- Precopy converged: iter=7 total=38671MiB curr=5418MiB bw=1720.46MiB/s transfer=3.15s overhead=66ms est_downtime=3142ms elapsed=22.68s avg_bw=1704.85MiB/s
...
cloud-hypervisor:  33.411682s: <vmm> INFO:vmm/src/lib.rs:1365 -- Precopy complete: iter=8 total=44339MiB curr=5668MiB bw=1799.98MiB/s transfer=3.15s overhead=66ms est_downtime=3142ms elapsed=23.82s avg_bw=1861.45MiB/s
```

# Outlook

We can add user-configurable downtimes and migration downtimes next.

These changes are inspired by [0] but differ significantly in details.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7033

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 13:18:07 +00:00
Philipp Schuster
beb5808406 vm-migration: add MemoryMigrationContext for precopy metrics
Introduce MemoryMigrationContext to track internal metrics of an ongoing
precopy memory migration.

The context aggregates information such as iteration count, transferred
bytes, durations, bandwidth, and estimated downtime. This enables
migration logic to make decisions based on runtime characteristics,
such as terminating iterations once the expected downtime is below a
target threshold.

The type is used in the next commit to implement iteration-based
migration metrics.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 13:18:07 +00:00
Philipp Schuster
447a4c236b vmm: migration: add code comment
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 13:18:07 +00:00
Philipp Schuster
1f5c5093e4 vmm: migration: refactor memory migration into iteration helpers
Refactor the precopy memory migration path into dedicated helpers that
handle the different migration phases:

  - initial full memory transfer
  - repeated dirty-page iterations while the VM is running
  - final iteration after the VM is paused

This separates concerns in the migration code and provides the
infrastructure needed for collecting migration metrics in the following
changes.

These changes are inspired by [0] but differ significantly in details.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7033

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 13:18:07 +00:00
Philipp Schuster
d4b5502472 vmm: reduce verbosity of dirty logging output
Lower several informational messages in the dirty logging path to
debug level.

These messages are noisy in practice and provide little value since
dirty logging is known to work reliably. More useful migration metrics
(e.g., dirty size per iteration) is logged per iteration in subsequent
commits.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 13:18:07 +00:00
Philipp Schuster
b90634a887 vmm: migration: emit lifecycle events
Emit migration lifecycle events via the event monitor.

This aligns migration with other VM lifecycle operations such as boot,
pause, and resume, allowing external management software to observe
migration progress consistently.

Events emitted:
  src:
    vm.migration-started
    vm.migration-finished
    vm.migration-failed
  dst:
    vm.migration-receive-started
    vm.migration-receive-finished
    vm.migration-receive-failed

Please note that these features are independent of an upcoming new
endpoint to fetch migration statistics.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 13:18:07 +00:00
Philipp Schuster
5c93bcf2d7 vmm: migration: only permit migration of running VMs
Currently, it is not possible to migrate a paused VM. It fails with
the following error:

```
[2026-03-09T14:43:42Z ERROR cloud_hypervisor] Fatal error: HttpApiClient(ServerResponse(InternalServerError, Some("[\"Error from API\",\"Error starting migration sender\",\"Failed to pause migratable component\",\"Invalid transition: InvalidStateTransition(Paused, Paused)\"]")))
Error: ch-remote exited with the following chain of errors:
  0: http client error
  1: Server responded with InternalServerError
  2: Error from API
  3: Error starting migration sender
  4: Failed to pause migratable component
  5: Invalid transition: InvalidStateTransition(Paused, Paused)
```

and even worse, after that, the VM is resumed on the source!

Make the behavior explicit by only allowing migration of VMs in the
Running state. This avoids unintended state transitions during
migration and clarifies the current expected semantics.

Future work could extend the migration protocol to work with paused VMs
and preserve the VM runtime state, allowing paused VMs to be migrated
without altering their state.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 13:18:07 +00:00
Philipp Schuster
b5169ff419 vmm: migration: flatten control flow in send_migration()
Move the error branch to the top and remove unnecessary nesting in
send_migration().

This change is purely mechanical and introduces no functional changes.
It simplifies the control flow and prepares the code for the following
migration-related improvements in this series.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 13:18:07 +00:00
Anatol Belski
3cbbce353e tests: Set image_type=raw for rate limiter block test images
The rate limiter tests create raw block images with dd but do not
specify image_type=raw. Without it the VMM autodetects the format
and enables sector 0 write protection for unknown image types,
causing I/O errors when fio writes to sector 0 and making the
test hang until timeout.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-13 11:14:00 +00:00
Muminul Islam
f6a1d821d7 tests: extend timeout for CVM tests
Confidential VMs require additional time during boot to load the IGVM
image, complete page measurements, and perform Reverse Map Table (RMP)
validation. In addition, PSP latency can further delay the boot
process. Extend the test timeout to accommodate these additional
initialization steps.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-13 08:59:00 +00:00
Muminul Islam
4ba2d770d1 scripts: update CVM test script to add thread
- Modified the integration test script to support CVM test threads
- Add more parameters to cargo nextest to match other files

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-13 08:58:15 +00:00
dependabot[bot]
7d582ec0f6 build: Bump docker/setup-qemu-action from 3 to 4
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-13 01:30:58 +00:00
Julian Schindel
e265543e3c misc: make MSRV workspace-wide for cloud-hypervisor dependencies
Moves the MSRV requirement to the workspace and expands it to all
cloud-hypervisor dependencies and dev-dependencies.
This improves discoverability for new contributors working on crates
other than the cloud-hypervisor itself and creates consistency regarding
the MSRV of cloud-hypervisor dependencies.
Functionally, this doesn't change anything for dependencies of the
cloud-hypervisor crate as the MSRV requirement is already enforced by CI
when building the cloud-hypervisor with the MSRV versioned compiler.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
2026-03-13 01:30:25 +00:00
Demi Marie Obenour
f630694bb0 virtio-devices: Use const fn to compute PCI BAR offsets
This is much less error-prone than manual computation.  No functional
change intended.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-03-13 01:30:10 +00:00
Philipp Schuster
001adbe15a hypervisor: mshv: cleanup unneeded Arc
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 01:20:25 +00:00
Philipp Schuster
a747e2b72a hypervisor: kvm: cleanup unneeded Arc
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-13 01:20:25 +00:00
Muminul Islam
4e7f9595c8 vmm: remove nested virtualization check for arm64/riscv64
Remove the architecture check that prevented nested virtualization
control on arm64 and riscv64. This allows nested virtualization to
be disabled where supported, particularly when using MSHV.

Note that on arm64 disabling nested virtualization may not fully
disable the capability depending on the underlying platform.
Use of this functionality is left to the user's discretion.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-12 20:52:36 +00:00
Sebastian Walz
2df41986b7 main: add .action(ArgAction::Append) to all .num_args(1..)
With `.num_args(1..)`, multiple values can be specified for a CLI
option, but the option cannot be specified more than once. In my
experience, it’s more common to specify flags with a single argument
multiple times to specify multiple arguments. One might thus expect to
call cloud-hypervisor with e.g. `--disk path=foo --disk path==bar`.

With this commit, both `--disk path=foo path=bar path=baz` and
`--disk path=foo -disk path=bar path=baz` (note: combinations as well)
are allowed.

Signed-off-by: Sebastian Walz <sebastian.walz@secunet.com>
2026-03-12 19:17:27 +00:00
Julian Schindel
84b8d25bb6 vm-migration: fix UB in MemoryRangeTable::read_from
The pointer created by `Vec::as_ptr` may not be used for mutation of the
underlying data [0].
This PR switches to `Vec::as_mut_ptr` and uses `cast` to avoid
mutability changes when casting.
Also improves safety reasoning, separates the unsafe call from the
call to `read_exact` to improve clarity and simplifies the vector
creation.

[0]: https://doc.rust-lang.org/alloc/vec/struct.Vec.html#method.as_ptr

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
2026-03-12 18:30:03 +00:00
Anatol Belski
32d339c59e block: qcow: Migrate debug/test helpers to BlockResult
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>
2026-03-12 14:18:45 +00:00
Anatol Belski
1accf47db4 block: qcow: Migrate convert() to BlockResult
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>
2026-03-12 14:18:45 +00:00
Anatol Belski
4930d93090 block: qcow: Migrate convert_reader() to BlockResult
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>
2026-03-12 14:18:45 +00:00
Anatol Belski
748666fe4d block: qcow: Migrate convert_reader_writer() to BlockResult
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>
2026-03-12 14:18:45 +00:00
Anatol Belski
c59c5687d2 block: qcow: Migrate convert_copy() to BlockResult
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>
2026-03-12 14:18:45 +00:00
Anatol Belski
732cddb8b3 block: qcow: Migrate dirty/corrupt bit helpers to BlockResult
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>
2026-03-12 14:18:45 +00:00
Anatol Belski
4fea912d18 block: qcow: Add open_disk_image helper with path context
Add a small helper in the block crate that opens a disk image
file and wraps any failure in a BlockError carrying the file
path and operation context. Use it from the vmm device manager
so that a failed open now reports which path couldn't be opened.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 14:18:45 +00:00
Anatol Belski
2bcbe25539 block: qcow: Add backing file path to qcow error context
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>
2026-03-12 14:18:45 +00:00
Anatol Belski
b1bc376c91 block: Make detect_image_type return BlockResult with context
Convert detect_image_type() from io::Result to BlockResult so
that I/O failures carry the operation name in the error context.
Update the corresponding vmm error variant to wrap BlockError.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 14:18:45 +00:00
Anatol Belski
58bdfaee3a block: qcow: QcowDiskSync returns BlockResult with path context
Change QcowDiskSync::new() to return BlockResult instead of
qcow::Result, mapping format specific errors to the appropriate
BlockErrorKind at the crate boundary. The vmm caller attaches
the disk image path to the error so failures identify which
file was being opened.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 14:18:45 +00:00
Anatol Belski
8c2794533d block: qcow: impl AsFd for RawFile and QcowRawFile
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>
2026-03-12 14:18:45 +00:00
Anatol Belski
9be154b03c block: Add BlockResult, From<io::Error>
Add the public BlockResult type alias and a From<io::Error>
impl so that bare I/O errors automatically convert into
BlockError with BlockErrorKind::Io via the ? operator.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 14:18:45 +00:00
Anatol Belski
4c29548736 block: Add BlockError constructors and builder methods
Add the construction and inspection API for BlockError,
consisting on constructors that accept a kind and optional
source, builder methods that attach context after
the fact, and accessors for retrieving the kind, context,
and typed source references. The builder pattern allows
callers to enrich errors at each level of the call stack.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 14:18:45 +00:00
Anatol Belski
d5467dca8e block: Add BlockError struct with Display and Error impls
Add the single public crate error type. It combines a
BlockErrorKind for classification, an optional boxed source
for the underlying cause, and an optional ErrorContext for
diagnostics. Display renders the kind and context only,
leaving source traversal to error reporters so the cause
chain is not duplicated in human readable output.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 14:18:45 +00:00
Anatol Belski
55504177cb block: Add ErrorContext for path/offset/op diagnostics
Add a struct that carries optional diagnostic metadata - file
path, byte offset, and operation name that can be attached
to any BlockError. This lets errors report *where* and *during
what* a failure occurred, which is especially useful when the
same I/O kind shows up at multiple call sites.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 14:18:45 +00:00
Anatol Belski
e4e74a9d93 block: Add BlockErrorKind classification enum
Add a small, stable enum that classifies block errors into
broad categories - I/O, invalid format, unsupported feature,
corrupt image, out of bounds, not found, overflow. Callers
match on this for control flow rather than on format specific
error variants.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 14:18:45 +00:00
Anatol Belski
f184a0f0f3 block: Add error module skeleton
Introduce error.rs as the home for a unified error hierarchy that
will replace the per format error types at the public crate
boundary. This commit is intentionally empty beyond the copyright
header and module declaration in lib.rs.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 14:18:45 +00:00
Emir Beganovic
623af62743 block: Implement write_zeroes and punch_hole for AIO backend
The AIO block backend advertises VIRTIO_BLK_F_WRITE_ZEROES
and VIRTIO_BLK_F_DISCARD to guests because the filesystem
probe (supports_sparse_operations) returns true on ext4/XFS.
However, RawFileAsyncAio::write_zeroes() and punch_hole()
return errors because Linux AIO (io_submit) has no IOCB
command for fallocate.

When io_uring is unavailable (e.g. io_uring_disabled=2, a
common security hardening on enterprise Linux), Cloud
Hypervisor falls back to the AIO backend. The guest
negotiates the feature, issues WRITE_ZEROES requests, and
gets I/O errors.

Implement write_zeroes and punch_hole using synchronous
libc::fallocate() calls, matching the pattern used by the
sync backend (RawFileSync). A VecDeque-based completion
list signals results to the caller via the existing eventfd
mechanism.

Unit tests mirror the existing raw_sync.rs test suite.
Integration tests add AIO-specific variants of the discard
and fstrim tests using _disable_io_uring=on.

Signed-off-by: Emir Beganovic <beganovic.emir@gmail.com>
2026-03-12 12:08:01 +00:00
dependabot[bot]
8671e193ff build: Bump docker/metadata-action from 5 to 6
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-12 12:28:31 +00:00
Anatol Belski
6850b04fa6 performance-metrics: Set image_type=qcow2 for remaining qcow2 test disks
Without it the VMM autodetects the format and logs
warnings that specifying image_type will become mandatory
for non raw images in the future.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 12:27:24 +00:00
Anatol Belski
766f4206c9 performance-metrics: Set image_type=raw for block I/O test image
Without explicit image_type fio first sequential write hits
sector 0 and gets VIRTIO_BLK_S_IOERR. fio then hangs, causing
block_write_MiBps and all other write tests using
BLK_IO_TEST_IMG to time out.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 12:27:24 +00:00
Anatol Belski
c0a81bc903 performance-metrics: Settle host before each test
Flush host writeback queues, drop the page cache and sleep 1s
for kernel housekeeping before each test run.

The cloud-hypervisor block backend does buffered I/O on the host
side, so dirty pages from prior write tests can accumulate and
compete for I/O bandwidth with subsequent tests. Dropping caches
ensures cold read tests get a consistent baseline rather than
benefiting from data cached by prior tests. The brief cooldown
lets the kernel finish tearing down KVM state and freeing pages
from the previous VM before the next one starts.

Requires root, which the metrics container provides. Silently
fails otherwise.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 12:27:24 +00:00
Anatol Belski
57e64a0848 performance-metrics: Kill stale processes on test timeout
When a test times out, the spawned thread containing the
cloud-hypervisor child process, iperf3/ethr sub processes,
and all associated resources (TAP devices, file descriptors,
hugepage reservations) is abandoned without cleanup. This
attaches a cleanup routine that kills cloud-hypervisor,
iperf3, and ethr processes on timeout, then waits
briefly for the kernel to reclaim their resources. This
prevents leaked processes from interfering with subsequent
tests.

Removes the existing TODO comment.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-12 12:27:24 +00:00
Philipp Schuster
5b55286099 virtio-devices: block: add another helpful log message
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-12 09:59:59 +00:00
Philipp Schuster
d90f852ddf virtio-devices: block: add helpful message on failed sector 0 write
We ran the Microsoft Windows installer in CHV (via network + VNC) and
the installation always failed when the installer wanted to write the
partition table. Since recently, for very good reason, sector 0 writes
are disabled if the image type is not set explicitly and only
implicitly auto-detected as raw [0].

To ease troubleshooting, I've added a descriptive log message. It is a
little spammy, but it is what is required to help users to troubleshoot.

[0] 6ecdf90e22

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-03-12 09:59:59 +00:00
Rob Bradford
0a4fe0e41e vmm: Fix OpenAPI definition of for lock granularity
Use CamelCase as per the existing definitions (which allows the removal
of the serde transformation)

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-12 02:10:43 +00:00
Rob Bradford
f211170fa2 vmm: openapi: Create enum types for enums
This makes it clearer that these are enums that can be reused and also
helps generation by providing names for those types.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-12 02:10:43 +00:00
wuxinyue
19d0196291 virtio-devices: block: Reduce latency in completion handling
Signal the guest before processing queue submissions to enable
earlier guest side completion event handling, reducing end-to-end
latency for block device operations.

FIO benchmarks show up to 7.4% bandwidth improvement at 16
iodepth and 4k block size with NVMe devices.

Signed-off-by: wuxinyue <wuxinyue.wxy@antgroup.com>
2026-03-10 09:06:20 +00:00
dependabot[bot]
40768086b9 build: Bump the non-rust-vmm group across 2 directories with 15 updates
Bumps the non-rust-vmm group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [libc](https://github.com/rust-lang/libc) | `0.2.182` | `0.2.183` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.21.0` | `1.22.0` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.40` | `0.8.42` |
| [getrandom](https://github.com/rust-random/getrandom) | `0.4.1` | `0.4.2` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.22` | `0.2.23` |
| [proc-macro-crate](https://github.com/bkchr/proc-macro-crate) | `3.4.0` | `3.5.0` |
| [quote](https://github.com/dtolnay/quote) | `1.0.44` | `1.0.45` |
| [uds_windows](https://github.com/haraldh/rust_uds_windows) | `1.1.0` | `1.2.0` |
| [winnow](https://github.com/winnow-rs/winnow) | `0.7.14` | `0.7.15` |

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

| Package | From | To |
| --- | --- | --- |
| [libc](https://github.com/rust-lang/libc) | `0.2.182` | `0.2.183` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.21.0` | `1.22.0` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.40` | `0.8.42` |
| [proc-macro-crate](https://github.com/bkchr/proc-macro-crate) | `3.4.0` | `3.5.0` |
| [quote](https://github.com/dtolnay/quote) | `1.0.44` | `1.0.45` |
| [winnow](https://github.com/winnow-rs/winnow) | `0.7.14` | `0.7.15` |



Updates `libc` from 0.2.182 to 0.2.183
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.183/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.182...0.2.183)

Updates `uuid` from 1.21.0 to 1.22.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.21.0...v1.22.0)

Updates `zerocopy` from 0.8.40 to 0.8.42
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.40...v0.8.42)

Updates `getrandom` from 0.4.1 to 0.4.2
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.4.1...v0.4.2)

Updates `jiff` from 0.2.22 to 0.2.23
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.22...jiff-static-0.2.23)

Updates `jiff-static` from 0.2.22 to 0.2.23
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.22...jiff-static-0.2.23)

Updates `proc-macro-crate` from 3.4.0 to 3.5.0
- [Release notes](https://github.com/bkchr/proc-macro-crate/releases)
- [Commits](https://github.com/bkchr/proc-macro-crate/compare/v3.4.0...v3.5.0)

Updates `quote` from 1.0.44 to 1.0.45
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.44...1.0.45)

Updates `rand_core` from 0.9.5 to 0.10.0
- [Release notes](https://github.com/rust-random/rand_core/releases)
- [Changelog](https://github.com/rust-random/rand_core/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand_core/commits/v0.10.0)

Updates `toml_datetime` from 0.7.5+spec-1.1.0 to 1.0.0+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.7.5...toml_datetime-v1.0.0)

Updates `toml_edit` from 0.23.10+spec-1.0.0 to 0.25.4+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.23.10...v0.25.4)

Updates `uds_windows` from 1.1.0 to 1.2.0
- [Release notes](https://github.com/haraldh/rust_uds_windows/releases)
- [Changelog](https://github.com/haraldh/rust_uds_windows/blob/master/CHANGELOG.md)
- [Commits](https://github.com/haraldh/rust_uds_windows/compare/v1.1.0...v1.2.0)

Updates `winnow` from 0.7.14 to 0.7.15
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.7.14...v0.7.15)

Updates `zerocopy-derive` from 0.8.40 to 0.8.42
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.40...v0.8.42)

Updates `libc` from 0.2.182 to 0.2.183
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.183/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.182...0.2.183)

Updates `uuid` from 1.21.0 to 1.22.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.21.0...v1.22.0)

Updates `zerocopy` from 0.8.40 to 0.8.42
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.40...v0.8.42)

Updates `rand` from 0.9.2 to 0.10.0
- [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/rand_core-0.9.2...0.10.0)

Updates `proc-macro-crate` from 3.4.0 to 3.5.0
- [Release notes](https://github.com/bkchr/proc-macro-crate/releases)
- [Commits](https://github.com/bkchr/proc-macro-crate/compare/v3.4.0...v3.5.0)

Updates `quote` from 1.0.44 to 1.0.45
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.44...1.0.45)

Updates `rand_core` from 0.9.5 to 0.10.0
- [Release notes](https://github.com/rust-random/rand_core/releases)
- [Changelog](https://github.com/rust-random/rand_core/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand_core/commits/v0.10.0)

Updates `toml_datetime` from 0.7.5+spec-1.1.0 to 1.0.0+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.7.5...toml_datetime-v1.0.0)

Updates `toml_edit` from 0.23.10+spec-1.0.0 to 0.25.4+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.23.10...v0.25.4)

Updates `winnow` from 0.7.14 to 0.7.15
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.7.14...v0.7.15)

Updates `zerocopy-derive` from 0.8.40 to 0.8.42
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.40...v0.8.42)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.183
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: getrandom
  dependency-version: 0.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.23
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.23
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: proc-macro-crate
  dependency-version: 3.5.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: quote
  dependency-version: 1.0.45
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand_core
  dependency-version: 0.10.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: toml_datetime
  dependency-version: 1.0.0+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.25.4+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: uds_windows
  dependency-version: 1.2.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: winnow
  dependency-version: 0.7.15
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.42
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.183
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand
  dependency-version: 0.10.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: proc-macro-crate
  dependency-version: 3.5.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: quote
  dependency-version: 1.0.45
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand_core
  dependency-version: 0.10.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: toml_datetime
  dependency-version: 1.0.0+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.25.4+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: winnow
  dependency-version: 0.7.15
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.42
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 01:03:26 +00:00
sohakpt
a179f67b20 main: Fix formatting issue in arguments
Signed-off-by: sohakpt <sohakpt@outlook.com>
2026-03-09 22:17:08 +00:00
Wei Liu
26fac58a7c docs: CONTRIBUTING.md: Use the up to date link for DCO
Since the project claims to follow Linux's process, update the link to
point to Linux's process, instead of relying on an archived page which
contains outdated information.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-03-09 22:11:30 +00:00
Anatol Belski
fd6891db62 block: qcow: Extend unit tests
Add tests for multiqueue concurrent reads, raw and QCOW2 backing
files, three layer backing chains, COW on partial cluster writes,
discard with backing fallthrough, cross cluster boundary operations,
reads beyond virtual size, and resize.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-09 13:02:07 +00:00
Anatol Belski
57e89b04f6 block: qcow: Refactor BackingFile for ownership based decomposition
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>
2026-03-09 13:02:07 +00:00
Anatol Belski
a4a5b19f64 block: qcow: Add resize() to QcowMetadata
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>
2026-03-09 13:02:07 +00:00
Anatol Belski
9d686b0866 block: qcow: Add QcowMetadata with RwLock
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>
2026-03-09 13:02:07 +00:00
Anatol Belski
63db385c3c block: qcow: Extract utility functions into util.rs
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>
2026-03-09 13:02:07 +00:00
Anatol Belski
210514cbf3 block: qcow: Extract QcowHeader and related types into header.rs
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>
2026-03-09 13:02:07 +00:00
Andrei Vagin
2698cfed98 virtio-devices: console: Fix descriptor chain processing
Both process_input_queue and process_output_queue were only processing
the first descriptor of a chain, leading to data loss if the driver used
chained descriptors.

This change iterates through all descriptors in a chain. It also moves
the flush call out of the descriptor loops to improve performance.

Signed-off-by: Andrei Vagin <avagin@google.com>
2026-03-09 05:19:11 +00:00
Andrei Vagin
49156c720c virtio-devices: console: Use 0 as the 'len' for the transmit queue
For a transmit queue (guest to host), the host only reads from the
guest-provided buffers and does not write to them. According to the
virtio specification (e.g., Section 2.6.8 in Virtio 1.1), the 'len'
field in the used ring has to be set to the number of bytes written to
the buffers. Therefore, it should be 0 for the console transmit queue.

Signed-off-by: Andrei Vagin <avagin@google.com>
2026-03-09 05:19:11 +00:00
Andrei Vagin
9f569c7b8d virtio-devices: console: Fix atomic corruption in ConsoleResizer
The update_console_size method was using fetch_and on the acked_features
atomic, which modified the atomic and cleared other feature bits.
Changed it to use a non-destructive load and bitwise AND.

Signed-off-by: Andrei Vagin <avagin@google.com>
2026-03-09 05:19:11 +00:00
Victor Vieux
01e4053bef docs: Add disk locking documentation
Add docs/disk_locking.md explaining advisory OFD locking, the
lock_granularity parameter, byte-range vs whole-file semantics,
and fallback behavior.

Signed-off-by: Victor Vieux <vieux@repl.it>
2026-03-08 10:07:03 +00:00
Victor Vieux
7c690ffec0 vmm: config: Expose disk lock granularity option
Add a per-disk lock_granularity parameter that lets users choose
between byte-range OFD locks and whole-file OFD locks:

  --disk path=/foo.img,lock_granularity=byte-range
  --disk path=/bar.img,lock_granularity=full

Byte-range is the default and matches QEMU behavior, working
best with storage backends where whole-file OFD locks are treated
as mandatory. The full option restores the original whole-file
locking for environments that depend on it.

The LockGranularityChoice enum and its FromStr impl live in the
block crate alongside the existing LockGranularity type. The
Block device converts the user-facing choice to the internal
LockGranularity at lock time, keeping device_manager.rs simple.

Closes: #7553

Signed-off-by: Victor Vieux <vieux@repl.it>
2026-03-08 10:07:03 +00:00
Anatol Belski
da0d0a2090 virtio-devices: Rename VIRTIO_F_IOMMU_PLATFORM
Rename the transport feature bit constant from
VIRTIO_F_IOMMU_PLATFORM to VIRTIO_F_ACCESS_PLATFORM across
the entire virtio-devices crate.

The virtio specification as of v1.1 carries bit 33 as
VIRTIO_F_ACCESS_PLATFORM. The Linux kernel UAPI header
<linux/virtio_config.h> carries VIRTIO_F_IOMMU_PLATFORM
only as a backward-compatible alias.

This is a pure rename with no functional or behavioral
change.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-08 09:56:01 +00:00
Anatol Belski
313d980538 vm-virtio: Remove stale comment from watchdog device ID
Device ID 35 for virtio watchdog was officially allocated and
merged into the Linux kernel UAPI headers as VIRTIO_ID_WATCHDOG
in kernel 5.15. The virtio specification v1.2 also lists device
ID 35 for the watchdog device type. Leaving the comment is
therefore misleading.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-08 09:56:01 +00:00
Anatol Belski
355cbad09a virtio-devices: iommu: Fix VirtioIommuConfig reserved field size
Fix the _reserved field in VirtioIommuConfig from [u8; 7] to
[u8; 3], correcting the struct size from 44 bytes to the
spec-mandated 40 bytes.

The virtio specification v1.2, Section 5.13.4 defines struct
virtio_iommu_config as 40 bytes total.

The kernel UAPI header linux/virtio_iommu.h matches this
layout with __u8 reserved[3] since kernel 5.17. Prior to that,
the struct was 36 bytes with no bypass field at all.

The incorrect [u8; 7] made the packed struct 44 bytes. Since
the struct is exposed to the guest, the guest saw a 44 byte
device specific configuration region instead of 40 bytes.
While well behaved guest drivers only access fields at known
offsets and would not observe data corruption from the extra
4 zero bytes at the tail, the oversized config region is a
spec violation.

The write_config path is not affected because it validates
the exact offset of the bypass field before allowing writes,
and the bypass field sits at offset 36 regardless of the
trailing reserved size.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-08 09:56:01 +00:00
dependabot[bot]
e5be2196f5 build: Bump docker/build-push-action from 6 to 7
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-07 00:50:03 +00:00
Rob Bradford
072e980e08 vmm: serial_manager: Use OwnedFd for the epoll FD
Replace the use of a conventional File for managing the lifetime.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-06 21:04:14 +00:00
Rob Bradford
e7e5fefb29 vmm: serial_manager: Improve event variable name
Name the variable after what the intention on the caller side not the
callee name.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-06 21:04:14 +00:00
Rob Bradford
4c2b2110c9 vmm: serial_manager: Reorder epoll event setup
It makes most sense to create the epoll FD and add the kill event before
identifying the transport specific fd.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-06 21:04:14 +00:00
Rob Bradford
9d712a10a6 vmm: Improve naming of ConsoleInfo struct members
These aren't FDs and we don't need to know that they are the main ones.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-06 21:04:14 +00:00
Rob Bradford
9cb49a244e vmm: device_manager: Be consistent with transport variable name
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-06 21:04:14 +00:00
Rob Bradford
f3d2d6c669 vmm: serial_manager: Be consistent with ConsoleTransport variable
Ensure that the same variable name is used for the transport.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-06 21:04:14 +00:00
Rob Bradford
48dcfc5fd0 vmm: Rename ConsoleOutput to ConsoleTransport
This is not just used for determine the output but also the input to the
console where this can be bidirectional.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-06 21:04:14 +00:00
Rob Bradford
5e2539ae16 vmm: serial_manager: Use more appropriately named variable
This isn't an fd - rather it's the UnixListener struct.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-06 21:04:14 +00:00
Anatol Belski
4ebbbe2294 vmm: igvm: Use sort_by_key instead of sort_by
Do the necessary replacement to satisfy clippy::unnecessary_sort_by.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-06 12:32:38 +00:00
Anatol Belski
e61349c10e vmm: gdb: Use map_or instead of map().unwrap_or()
Do the necessary replacement to satisfy clippy::map_unwrap_or.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-06 12:32:38 +00:00
Anatol Belski
3c62fabfc3 vmm: Collapse nested if into match arm guards
Do the necessary replacements to satisfy clippy::collapsible_match.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-06 12:32:38 +00:00
Anatol Belski
f1c33afc8e virtio-devices: vsock: Simplify discarded accept result
Do the necessary replacements to satisfy clippy::map_unwrap_or.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-06 12:32:38 +00:00
Anatol Belski
89107d2db4 arch: x86_64: Allow unused_unsafe on cpuid match arms
The nightly compiler used by cargo fuzz no longer requires unsafe
for __cpuid intrinsics, but stable still does.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-06 12:32:38 +00:00
Anatol Belski
ff39c35ac2 arch: x86_64: Collapse nested if into match arm guards
Do the necessary replacements to satisfy clippy::collapsible_match.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-06 12:32:38 +00:00
Anatol Belski
b7b38df99c block: raw: Use map_or instead of map().unwrap_or()
Do the necessary replacements to satisfy clippy::map_unwrap_or.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-06 12:32:38 +00:00
dependabot[bot]
5925a013af build: Bump docker/setup-buildx-action from 3 to 4
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-06 01:43:36 +00:00
Wei Liu
2495fdc0e7 vmm: Print out the version information at launch
It is useful to see this information in the log while debugging issues.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-03-06 00:40:51 +00:00
Anatol Belski
1f93fef66f vmm: Remove dead QcowDeviceCreate error variant
The variant has been unused since commit 12e20effd which
replaced direct QcowFile creation with QcowDiskSync.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-06 00:05:33 +00:00
dependabot[bot]
a758f8bd82 build: Bump docker/login-action from 3 to 4
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-05 00:51:33 +00:00
Rob Bradford
67945b31e5 tests: Add integration test for nested virtualization
Since we run integration tests on Intel & AMD this should test the
behaviour of `--cpus nested={on|off}` correctly.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-04 13:17:36 +00:00
Rob Bradford
f57b7c5b86 arch: x86_64: Correctly disable nested virtualization on AMD
The loop that is for programming the APIC ID and disabling nested
virtualization was prematurely breaking out on AMD platforms as the 0x1
leaf is also valid on AMD. This lead to the code attempting to disable
SVM in the 0x8000_0001 leaf never being reached.

Now only break out early if the CPU vendor is Intel.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-03-04 13:17:36 +00:00
Muminul Islam
7c48aafb65 tests: Add CVM HTTP API shutdown and delete tests
Add test_api_http_shutdown and test_api_http_delete to the
common_cvm module using GuestFactory with 4 CPUs. Both tests
reuse existing _test_api_shutdown and _test_api_delete helpers
to extend API coverage to confidential VMs.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-03 09:09:34 +00:00
Muminul Islam
f6829561e5 tests: Use GuestFactory in CVM simple launch test
Replace manual Guest field assignments with
GuestFactory::new_confidential_guest_factory() in
test_focal_simple_launch for consistent CVM guest creation.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-03 09:09:34 +00:00
Muminul Islam
b8a6afd332 tests: Add CVM HTTP API create/boot test
Add test_api_http_create_boot to the common_cvm module using
GuestFactory::new_confidential_guest_factory() with 4 CPUs.
This extends API create/boot coverage to confidential VMs.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-03 09:09:34 +00:00
Muminul Islam
7a0019514f tests: Make api_create_body() parameterless
Refactor api_create_body() to read cpu_count, kernel_path,
and kernel_cmdline from Guest fields instead of taking them
as parameters. This makes Guest the single source of truth
for VM configuration.

Update all call sites in HTTP and DBus API tests to use the
new parameterless signature. Switch guest creation to use
GuestFactory for consistent 4-CPU configuration.

Replace manual CPU and memory assertions with
validate_cpu_count() and validate_memory() helpers.
Replace thread::sleep with wait_vm_boot() in
_test_api_create_boot for proper boot synchronization.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-03 09:09:34 +00:00
Muminul Islam
f443bae9d9 tests: Support CVM in api_create_body()
Refactor api_create_body() to branch based on GuestVmType.
For confidential VMs, the JSON body now includes:
- platform config with sev_snp enabled
- IGVM payload with host_data instead of kernel
- nested virtualization disabled in CPU config

Replace the monolithic format! macro with incremental
push_str calls for clearer JSON construction.

Also improve error handling in GuestCommand by replacing
an unwrap() with a descriptive expect() on the IGVM path.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-03 09:09:34 +00:00
Muminul Islam
85159c9255 tests: Add factory pattern to create guest instances
Introduce GuestFactory struct that encapsulates GuestVmType,
boot timeout, and nested virtualization defaults. This avoids
repeatedly specifying VM type and associated defaults when
constructing Guest instances in integration tests.

Two factory constructors are provided:
- new_regular_guest_factory: default timeout, nested enabled
- new_confidential_guest_factory: CVM timeout, nested disabled

Multiple create_guest variants allow customizing CPU count,
memory size, and nested virtualization while inheriting the
factory's VM type and timeout settings.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-03 09:09:34 +00:00
Muminul Islam
f2d3c17e1f test_infra: set default kernel path and cmdline in Guest
Initialize kernel_path and kernel_cmdline with standard
direct boot defaults in the Guest constructor instead of
None. This removes boilerplate from individual tests that
use the common direct kernel boot configuration.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-03 09:09:34 +00:00
Muminul Islam
838a4f86c4 tests: move shared constants and helpers to test_infra
Move test constants (MAX_NUM_PCI_SEGMENTS,
DIRECT_KERNEL_BOOT_CMDLINE, CONSOLE_TEST_STRING),
arch-specific image name modules (x86_64, aarch64),
and helper functions (direct_kernel_boot_path, edk2_path)
from integration.rs to test_infra/src/lib.rs.

This centralizes shared test definitions so they can be
reused across multiple test crates instead of being
confined to integration.rs.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-03-03 09:09:34 +00:00
dependabot[bot]
511e682909 build: Bump the non-rust-vmm group across 2 directories with 12 updates
Bumps the non-rust-vmm group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_with](https://github.com/jonasbb/serde_with) | `3.16.1` | `3.17.0` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.39` | `0.8.40` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.21` | `0.2.22` |
| libredox | `0.1.12` | `0.1.14` |
| [libz-sys](https://github.com/rust-lang/libz-sys) | `1.1.23` | `1.1.24` |
| [pin-project-lite](https://github.com/taiki-e/pin-project-lite) | `0.2.16` | `0.2.17` |
| [piper](https://github.com/smol-rs/piper) | `0.2.4` | `0.2.5` |
| [regex-syntax](https://github.com/rust-lang/regex) | `0.8.9` | `0.8.10` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.25.0` | `3.26.0` |

Bumps the non-rust-vmm group with 2 updates in the /fuzz directory: [serde_with](https://github.com/jonasbb/serde_with) and [zerocopy](https://github.com/google/zerocopy).


Updates `serde_with` from 3.16.1 to 3.17.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.16.1...v3.17.0)

Updates `zerocopy` from 0.8.39 to 0.8.40
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.39...v0.8.40)

Updates `jiff` from 0.2.21 to 0.2.22
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.21...jiff-static-0.2.22)

Updates `jiff-static` from 0.2.21 to 0.2.22
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.21...jiff-static-0.2.22)

Updates `libredox` from 0.1.12 to 0.1.14

Updates `libz-sys` from 1.1.23 to 1.1.24
- [Release notes](https://github.com/rust-lang/libz-sys/releases)
- [Commits](https://github.com/rust-lang/libz-sys/compare/1.1.23...1.1.24)

Updates `pin-project-lite` from 0.2.16 to 0.2.17
- [Release notes](https://github.com/taiki-e/pin-project-lite/releases)
- [Changelog](https://github.com/taiki-e/pin-project-lite/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/pin-project-lite/compare/v0.2.16...v0.2.17)

Updates `piper` from 0.2.4 to 0.2.5
- [Release notes](https://github.com/smol-rs/piper/releases)
- [Changelog](https://github.com/smol-rs/piper/blob/main/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/piper/compare/v0.2.4...v0.2.5)

Updates `regex-syntax` from 0.8.9 to 0.8.10
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-syntax-0.8.9...regex-syntax-0.8.10)

Updates `serde_with_macros` from 3.16.1 to 3.17.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.16.1...v3.17.0)

Updates `tempfile` from 3.25.0 to 3.26.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits/v3.26.0)

Updates `zerocopy-derive` from 0.8.39 to 0.8.40
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.39...v0.8.40)

Updates `serde_with` from 3.16.1 to 3.17.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.16.1...v3.17.0)

Updates `zerocopy` from 0.8.39 to 0.8.40
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.39...v0.8.40)

Updates `serde_with_macros` from 3.16.1 to 3.17.0
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.16.1...v3.17.0)

Updates `zerocopy-derive` from 0.8.39 to 0.8.40
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.39...v0.8.40)

---
updated-dependencies:
- dependency-name: serde_with
  dependency-version: 3.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.40
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.22
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.22
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libredox
  dependency-version: 0.1.14
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libz-sys
  dependency-version: 1.1.24
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: pin-project-lite
  dependency-version: 0.2.17
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: piper
  dependency-version: 0.2.5
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: regex-syntax
  dependency-version: 0.8.10
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with_macros
  dependency-version: 3.17.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: tempfile
  dependency-version: 3.26.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.40
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with
  dependency-version: 3.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.40
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_with_macros
  dependency-version: 3.17.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.40
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-03 02:08:01 +00:00
Anatol Belski
aae7594a67 tests: Add direct I/O block device alignment test
Boot a VM with a 4k sector loop device passed with direct=on and
image_type=raw.  Assert that the guest sees a 4096 byte logical
sector and that a DIO write/read roundtrip at 4096 byte alignment
succeeds.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-02 18:26:55 +00:00
Anatol Belski
6fe3f63d27 tests: Add direct I/O file backed alignment test
Verify that DiskTopology::probe() returns the correct DIO alignment
for a regular file on a 4k sector filesystem.  The test creates a
loop device with --sector-size 4096, formats ext4, places a raw disk
image on it, and boots a VM with direct=on.  Asserts that the guest
sees a 4096 byte logical sector and that a DIO write/read roundtrip
succeeds.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-02 18:26:55 +00:00
Anatol Belski
ba889a6ec2 tests: Fix loop device race in create_loop_device
Move LOOP_CTL_GET_FREE + open + LOOP_CONFIGURE into the retry loop
so each attempt requests a fresh free device number.  Previously, a
parallel test could claim the same device between GET_FREE and
CONFIGURE, and retrying the same stale number would always fail with
EBUSY.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-02 18:26:55 +00:00
Anatol Belski
496c89c289 block: Add unit tests for DiskTopology file alignment probing
Test valid power of two alignment, layout compatibility,
direct helper coverage, and O_DIRECT write/read roundtrip.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-02 18:26:55 +00:00
Anatol Belski
15ce890dd3 block: Query actual DIO alignment for file backed images
DiskTopology::probe() returned a hardcoded 512 for regular files,
causing O_DIRECT failures on volumes with larger block sizes
(e.g. 4K).

Use statx(STATX_DIOALIGN) (Linux >= 6.1) to query the real per file
DIO memory and offset alignment. Unlike fstatvfs().f_bsize, which
only returns the filesystem preferred I/O block size,
STATX_DIOALIGN reports the true DIO constraints accounting for the
filesystem, underlying block device, and any stacking (loop, dm,
etc.).

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-02 18:26:55 +00:00
Anatol Belski
a94fa554c3 hypervisor: kvm: Fix nightly rustfmt import ordering
Nightly rustfmt now prefers `self` re-exports inline rather
than a separate 'pub use {kvm_bindings, kvm_ioctls}' line.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-03-01 18:07:22 +00:00
dependabot[bot]
d1c89a271d build: Bump crate-ci/typos from 1.43.5 to 1.44.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.5 to 1.44.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.43.5...v1.44.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-28 00:51:12 +00:00
Rob Bradford
5b5e5cb999 vmm: cpu: Avoid potential infinite loop during core scheduling setup
Avoid a potential infinite loop where if the leader fails to create a
cookie due to an unexpected error (not one of the SMT/no kernel support
errors) then the other vcpu threads will continue around their
spinloops.

This change also clarifies the state machine for the leader election
with an explicit enum.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-27 14:06:28 +00:00
Muminul Islam
b41927dbcc tests: cvm: remove unused GuestAddress import
Drop an unused vm_memory::GuestAddress import from common_cvm
in integration tests to keep the module clean.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-27 10:44:18 +00:00
Muminul Islam
a216cf164f tests: use default memory helper in integration tests
Replace hard-coded --memory size=512M args with default_memory()
across integration tests to centralize default memory settings.
This reduces duplicated CLI fragments and keeps behavior consistent.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-27 10:44:18 +00:00
Muminul Islam
c35bfbd79f tests: use default_cpus() across integration tests
Replace hard-coded --cpus boot=<n> arguments in integration tests
with GuestCommand::default_cpus() for shared, centralized defaults.

This removes duplicated CLI fragments and keeps CPU setup consistent.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-27 10:44:18 +00:00
Muminul Islam
af764235a0 tests: nested not supported for CVM on MSHV
Nested on MSHV confident VM not supported yet.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-27 10:44:18 +00:00
Muminul Islam
32edcf39a6 tests: centralize memory validation in test helpers
Replace the hard-coded memory threshold check in the simple launch
integration test with Guest::validate_memory(None).

Add Guest::get_expected_memory() to derive thresholds from mem_size_str
and vm_type, and reuse this through validate_memory().

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-27 10:44:18 +00:00
Muminul Islam
05aeef06e5 tests: centralize default memory args in helpers
Replace hard-coded --memory args in simple launch tests
with GuestCommand defaults driven by Guest state.
Add Guest.mem_size_str with a default of 512M and introduce
default_memory_string() and GuestCommand::default_memory().

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-27 10:44:18 +00:00
Muminul Islam
45a5c7a04e tests: validate CPU count in the test infra
Instead of validating number of CPU in the test case itself,
moving the checking of the CPU count to Guest struct with a
new function as The Guest already has the Default CPU number.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-27 10:44:18 +00:00
Muminul Islam
60c6242bde tests: centralize default CPU arguments in test infrastructure
Replace the hard-coded  .args(["--cpus", "boot=1"]) in the simple
launch integration test with a shared helper (default_cpus) from test
infrastructure.

Extend Guest with explicit CPU-related defaults (num_cpu, nested)
and add default_cpus_string() so CPU configuration is derived from
guest state instead of being duplicated at call sites.

This refactor improves consistency and makes CPU defaults easier to
maintain across integration tests.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-27 10:44:18 +00:00
Muminul Islam
fdc51d923f tests: refactor event sequencing expectations for simple launch
Move MetaEvent from the integration test into shared test infrastructure
and expose it for reuse. Add a Guest helper that returns the expected
sequential events for simple launch, and update the integration test to
consume this helper instead of maintaining a local event list.

Adjust expected behavior for confidential VMs by omitting the disk reset
event, which is not guaranteed to be emitted in that mode. Preserve the
existing expected sequence for non-confidential VMs.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-27 10:44:18 +00:00
dependabot[bot]
9fd9c24419 build: Bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-27 00:50:46 +00:00
Victor Vieux
110192087e vmm: config: Fix missing comma in NetConfig help text
The SYNTAX help string for --net was missing a comma between
pci_segment and offload_tso parameters, making the help output
show them as a single run-on token.

Signed-off-by: Victor Vieux <vieux@repl.it>
2026-02-26 21:48:55 +00:00
Saravanan D
00c05f4761 block: Use logical block size for alignment
O_DIRECT requires buffer addresses to be aligned to the backend
device's logical block size. The existing bounce buffer logic in
execute_async() hardcodes SECTOR_SIZE (512) for the alignment check
and bounce buffer allocation. This is insufficient for devices with
a 4096-byte logical block size, where misaligned buffers cause
-EINVAL from the host kernel.

Add an alignment() method to the AsyncIo trait that returns the
backend's logical block size, defaulting to SECTOR_SIZE. The three
raw I/O backends (io_uring, AIO, synchronous) probe the device
topology via DiskTopology::probe() at creation time and return the
actual logical block size. All image format backends would simply
use the default value of 512 bytes since their underlying are
not block devices.

execute_async() now queries disk_image.alignment() instead of using
the hardcoded SECTOR_SIZE

Fixes: #7720

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-26 15:47:17 +00:00
Rob Bradford
272fa624ef ci: Disable RISC-V workflows temporarily
The workflows are very flaky and have been failing the majority of the
time recently.

Fixes: #7758

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-26 15:33:28 +00:00
Rob Bradford
3f800d2bb4 vmm: Add core scheduling support for vCPU threads
Add a core_scheduling option to --cpus with three modes of operation.
This feature takes advantage of a kernel feature that restricts
scheduling of processes on the SMT threads on the same core. This is
useful for mitigating certain classes of side-channel attacks and has
better performance that disabling SMT on the CPU.

- vm (default): All vCPU threads share one core scheduling cookie.
  They may be co-scheduled on SMT siblings while host threads are
  excluded - this has minimal performance impact and can even
  potentially improve performance from co-location.
- vcpu: Each vCPU gets a unique cookie preventing any two vCPUs from
  sharing SMT siblings. This has the strongest isolation but at some
  compromise of performance.
- off: No core scheduling applied (old behaviour).

This isolation is done by the kernel maintaining a "cookie" - threads
with the same cookie can share the same core.

In vCPU mode each vCPU thread the cookie is created when the thread
starts and each gets a unique cookie. For VM mode the first vCPU thread
(the leader) will create the cookie. All other vCPU threads started (via
hotplug or during boot) will have that cookie shared to it.

EINVAL/ENODEV from prctl is silently ignored so this works transparently
on kernels older than 5.14 that lack PR_SCHED_CORE or when SMT disabled.

Full details of this kernel feature can be found at:
https://docs.kernel.org/admin-guide/hw-vuln/core-scheduling.html

This implementation was inspired by crosvm's implementation - in
particular the enable_core_scheduling() function.

This is challenging to test via integration testing but the logging of
the received cookie shows it working:

VM case:

cloud-hypervisor:   0.243102s: <vcpu1> INFO:vmm/src/cpu.rs:1247 -- vCPU 1: core scheduling cookie = 0x33e4c167
cloud-hypervisor:   0.243102s: <vcpu0> INFO:vmm/src/cpu.rs:1247 -- vCPU 0: core scheduling cookie = 0x33e4c167

vCPU case:

cloud-hypervisor:   0.089356s: <vcpu0> INFO:vmm/src/cpu.rs:1247 -- vCPU 0: core scheduling cookie = 0x13993ad6
cloud-hypervisor:   0.089380s: <vcpu1> INFO:vmm/src/cpu.rs:1247 -- vCPU 1: core scheduling cookie = 0xd48e86e

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-26 11:57:39 +00:00
Pascal Scholz
15d1f1d7fd vmm: Refactor locking in AddressManager::move_bar
The current implementation performs multiple operations on allocators in
a row, with the single goal of updating the allocator. For each of these
operations, the `Mutex` guarding the respective allocator is locked anew
which introduces room for race conditions.

Instead of locking the mutex multiple times, we should lock it once to
perform the whole move.

Signed-off-by: Pascal Scholz <pascal.scholz@cyberus-technology.de>
On-behalf-of: SAP pascal.scholz@sap.com
2026-02-26 11:56:59 +00:00
Muminul Islam
c9cf3294ea vmm: remove duplicate vm config parameter
Vm::create_device_manager accepted both config and _vm_config, but
both represented the same VM configuration source. Remove _vm_config
from the function signature and from its call site, and use config
for the TDX dynamic check.

This is a cleanup-only refactor with no intended functional change.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-26 00:44:11 +00:00
Anatol Belski
6404d2d513 block: Assume sparse support for block devices
There is no non destructive readonly ioctl to query block device
discard or write zeroes capabilities. BLKZEROOUT is guaranteed to
succeed via kernel software fallback. BLKDISCARD may fail at runtime
with EOPNOTSUPP on devices that lack trim support, but the error
propagates to the guest as VIRTIO_BLK_S_IOERR and well behaved
guests handle it gracefully.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-26 00:43:20 +00:00
Anatol Belski
e067c76880 tests: windows: Disable sparse for DM snapshot OS disks
The Windows tests use a DM snapshot device for the OS disk.
DM snapshot targets do not support BLKDISCARD, so the VMM returns
IOERR for every TRIM attempt. viostor.sys may BSOD when the host
returns an error for negotiated discard/write-zeroes operations.

Add a default_disks_sparse_off() helper to GuestCommand and use it
in all Windows tests.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-26 00:43:20 +00:00
Anatol Belski
8a165aef3b tests: block: Add integration test for DM snapshot discard failure
Verify that the guest remains stable when BLKDISCARD fails on the
host backend.  DM snapshot targets do not support discard, so the
VMM returns VIRTIO_BLK_S_IOERR.  The test retries blkdiscard several
times, checking guest responsiveness after each attempt, then
confirms normal I/O still works.

The DM topology follows the same pattern used by WindowsDiskConfig.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-26 00:43:20 +00:00
Anatol Belski
bc374c537c tests: block: Add test for block device discard on loop device
Verify that a loopback block device advertises
VIRTIO_BLK_F_DISCARD to the guest and that blkdiscard succeeds.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-26 00:43:20 +00:00
Anatol Belski
25a63c8b87 tests: block: Add trailing_args parameter to run_qemu_img()
Extend run_qemu_img() with an optional trailing_args parameter
for arguments that follow the image path, such as the size in
'qemu-img create -f raw <path> 128M'.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-26 00:43:20 +00:00
Demi Marie Obenour
989f3a3233 misc: generic vhost-user: reject virtio device IDs that cannot work
Some virtio devices cannot be implemented via vhost-user because they
require tight integration with the VMM.  This includes the IOMMU and
watchdog devices.

An attempt to create a generic vhost-user device with one of these IDs
is always either a bug or human error.  To aid debugging, return a
helpful error message rather than silently continuing.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
Demi Marie Obenour
7ea1fa07a2 misc: generic vhost-user: support human-readable device ID
This avoids having to pass the numeric device ID, which is not
very meaningful to humans.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
Demi Marie Obenour
042d1abd67 docs: generic vhost-user: document
Include documentation for the generic vhost-user device.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
Demi Marie Obenour
d6b80d9845 tests: generic vhost-user: add support
Include integeration tests for generic vhost-user devices.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
Demi Marie Obenour
36371283c6 ch-remote: add generic vhost-user support
Support adding generic vhost-user devices via the ch-remote CLI.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
Demi Marie Obenour
cfb69c68d2 virtio-devices: generic vhost-user: add D-Bus API
Allow adding and removing generic vhost-user devices via D-Bus.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
Demi Marie Obenour
df86b2864b vmm: add HTTP API endpoints for generic vhost-user
This includes OpenAPI schemas.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
Demi Marie Obenour
d510f11a50 misc: generic-vhost-user: wire up to command line
Support adding generic vhost-user devices via the Cloud Hypervisor
command line.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
Demi Marie Obenour
085a7a49fa vmm: generic vhost-user: add support
Add VMM support for generic vhost-user devices.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
Demi Marie Obenour
8c618ff5e0 virtio-devices: generic-vhost-user: implement device
This implements a generic vhost-user device.  All information about this
device must be provided to Cloud Hypervisor via the command-line or API.
The main use-case is types of vhost-user devices Cloud Hypervisor
doesn't know about, but it can also be used for types it does know
about.

The generic device delegates all configuration space handling to the
backend.  This means that the vhost-user backend must support
configuration space access.  It also means that the backend has control
of configuration space.  For instance, this means that setting the tag
of a virtio-fs device on the virtiofsd command line works as expected.

If the VM is snapshotted or migrated, the backend must write the
configuration space to a separate save file or migration stream.
Similarly, if the VM is restored or migrated, the backend must read the
configuration space from a separate save file or migration stream.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-24 07:53:53 +00:00
dependabot[bot]
8a09b3870c build: Bump the non-rust-vmm group across 2 directories with 12 updates
Bumps the non-rust-vmm group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.101` | `1.0.102` |
| [clap](https://github.com/clap-rs/clap) | `4.5.59` | `4.5.60` |
| [zbus](https://github.com/z-galaxy/zbus) | `5.13.2` | `5.14.0` |
| [bumpalo](https://github.com/fitzgen/bumpalo) | `3.19.1` | `3.20.2` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.20` | `0.2.21` |
| [rustix](https://github.com/bytecodealliance/rustix) | `1.1.3` | `1.1.4` |
| [syn](https://github.com/dtolnay/syn) | `2.0.116` | `2.0.117` |

Bumps the non-rust-vmm group with 4 updates in the /fuzz directory: [anyhow](https://github.com/dtolnay/anyhow), [clap](https://github.com/clap-rs/clap), [bumpalo](https://github.com/fitzgen/bumpalo) and [syn](https://github.com/dtolnay/syn).


Updates `anyhow` from 1.0.101 to 1.0.102
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.101...1.0.102)

Updates `clap` from 4.5.59 to 4.5.60
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.59...clap_complete-v4.5.60)

Updates `zbus` from 5.13.2 to 5.14.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus-5.13.2...zbus-5.14.0)

Updates `bumpalo` from 3.19.1 to 3.20.2
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.19.1...v3.20.2)

Updates `clap_builder` from 4.5.59 to 4.5.60
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.59...v4.5.60)

Updates `jiff` from 0.2.20 to 0.2.21
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.20...jiff-static-0.2.21)

Updates `jiff-static` from 0.2.20 to 0.2.21
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.20...jiff-static-0.2.21)

Updates `rustix` from 1.1.3 to 1.1.4
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v1.1.3...v1.1.4)

Updates `syn` from 2.0.116 to 2.0.117
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.116...2.0.117)

Updates `zbus_macros` from 5.13.2 to 5.14.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus_macros-5.13.2...zbus_macros-5.14.0)

Updates `zvariant` from 5.9.2 to 5.10.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant-5.9.2...zvariant-5.10.0)

Updates `zvariant_derive` from 5.9.2 to 5.10.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant_derive-5.9.2...zvariant_derive-5.10.0)

Updates `anyhow` from 1.0.101 to 1.0.102
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.101...1.0.102)

Updates `clap` from 4.5.59 to 4.5.60
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.59...clap_complete-v4.5.60)

Updates `bumpalo` from 3.19.1 to 3.20.2
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.19.1...v3.20.2)

Updates `clap_builder` from 4.5.59 to 4.5.60
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.59...v4.5.60)

Updates `syn` from 2.0.116 to 2.0.117
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.116...2.0.117)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.102
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.5.60
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zbus
  dependency-version: 5.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: bumpalo
  dependency-version: 3.20.2
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.60
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.21
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.21
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rustix
  dependency-version: 1.1.4
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.117
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zbus_macros
  dependency-version: 5.14.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zvariant
  dependency-version: 5.10.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zvariant_derive
  dependency-version: 5.10.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: anyhow
  dependency-version: 1.0.102
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.5.60
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bumpalo
  dependency-version: 3.20.2
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.60
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.117
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-24 01:44:47 +00:00
Anatol Belski
6f19d0071d block: vhdx: Fix multiqueue data corruption
Wrap the Vhdx instance in Arc<Mutex<>> so that all queues share
a single mutex-protected backend, matching the approach already
used for QCOW2.

Vhdx::clone() uses dup() which shares the kernel file description
including the file offset. With multiple queues performing
concurrent seek+read/write on the shared offset, I/O operations
race and corrupt data.

Fixes: #7665

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-23 18:25:21 +00:00
Anatol Belski
184a229ca4 tests: vhdx: Enable VHDX fstrim integration test
Remove the #[ignore] attribute from
test_virtio_block_fstrim_unsupported_vhdx.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-23 18:25:21 +00:00
Anatol Belski
600e74f0af scripts: dev_cli.sh: Allow passing commands to shell subcommand
Accept arguments after -- in 'dev_cli.sh shell' and forward them
to 'bash -c' inside the container. When no arguments are given,
an interactive shell is started as before. This enables running
one-off commands in the CI container without an interactive session,
for example:

  ./scripts/dev_cli.sh shell -- rustup toolchain install nightly \&\& cargo +nightly fmt --all -- --check

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-23 16:07:23 +00:00
Anatol Belski
ec5374cd99 performance-metrics: Set image_type for backing file tests
Add explicit image_type=qcow2 along backing_files=on
for the relevant QCOW2 perf tests.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-22 21:05:25 +00:00
Bo Chen
8aaf3734aa build: Temporarily remove the vfio CI worker
This runner machine is out for maintenance.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-02-21 08:51:12 +00:00
Wei Liu
52b2ebb2b8 vmm: api: Fix image_type in OpenAPI definition
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-02-21 08:50:31 +00:00
Bo Chen
00e106e53e build: Release v51.0
This release contains security fixes. Details can be found in the
release notes.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-02-19 16:28:52 -08:00
Rob Bradford
a63315df54 virtio-devices, block: Reject sector 0 discard/"write zeroes" requests
As well as rejecting writes to sector 0 in the case of raw files where
the user hasn't specified the image_type also reject virtio requests of
type discard and write_zeroes.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-19 22:43:15 +00:00
Rob Bradford
76e233504b vhost_user_block: Disable use of backing files in test implementation
Remove the use of QCOW2 backing files in the test implementation used
for CI.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-19 22:43:15 +00:00
Rob Bradford
b3e8e2abc5 vmm, virtio-devices: Deny zero sector writes for autodetected raw images
If the disk image was autodetected to raw (not specified with image_type
= 0) then in the virtio-block subsystem generate errors for writes to
block 0 (treat as if read-only). This gives an immediate error vs using
the image implementations in the block subsystem.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-19 22:43:15 +00:00
Rob Bradford
6f2357c14e vmm: Improve resiliency of image type handling
Add an image_type to DiskConfig to specify the image type. If none is
specified autodetect the image type but disable potentially unsafe
behaviour in the QCOW2 backend by disabling the backing file support.

If the image type is autodetected then fix it in the config so that it
will be persistant across reboots and migrations/snapshot & restores.
This also handles the case where the image type was not specified as
part of the disk configuration.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-19 22:43:15 +00:00
Anatol Belski
e36096db3e vmm: openapi: Sync DiskConfig OpenAPI spec
Add backing_files and sparse fields to the REST API.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-19 19:09:03 +00:00
Anatol Belski
4d30ba12c8 block: qcow: Add test for BackingFilesDisabled error
Verify that opening a QCOW2 image with a backing file reference
through QcowDiskSync with backing_files=off produces the user-facing
BackingFilesDisabled error rather than MaxNestingDepthExceeded.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-19 13:48:40 +00:00
Anatol Belski
94368c622e block: Add BackingFilesDisabled error for actionable user guidance
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>
2026-02-19 13:48:40 +00:00
Anirudh Rayabharam
68f746fbea hypervisor: mshv: use the new default partition args function
The 0.6.7 version of the mshv crates introduced a new version of
make_default_partition_create_arg inside `struct Mshv`. This version
queries the available processor features on the host and gives the same
feature set to the guests.

Move Cloud Hypervisor to this new function.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-02-18 13:58:08 +00:00
Rob Bradford
003ef780e0 build: Bump to latest igvm released version 0.4.0
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-18 13:45:13 +00:00
Saravanan D
17b0749e1c tests: Fix Generic Initiator integration test
test_guest_numa_generic_initiator was missing the #[test] attribute and
did not match the VFIO CI filter pattern "vfio::test_nvidia"

Add #[test] and rename to test_nvidia_guest_numa_generic_initiator so
the existing CI infrastructure picks it up on the vfio-nvidia runner.

Fixes: #7718

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-18 11:13:07 +00:00
Saravanan D
924baa9c0f vmm: Forward device_id from NumaConfig to NumaNode
The device_id field was added to both NumaConfig and NumaNode as part
of the Generic Initiator support, but create_numa_nodes() change
was missed when the commits were reorganized.

As a result, node.device_id is never propogated from the config to
the runtime node and the ACPI SRAT Type 5 (Generic Initiator Affinity)
entries were never emitted.

Add the missing propogation so that create_srat_table() can resolve
the device and emit the correct affinity structure

Fixes: #7717

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-18 11:13:07 +00:00
Wei Liu
0a5e79afce vmm: api: Expose the nested option in API description
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-02-17 23:39:44 +00:00
dependabot[bot]
08f338bcf6 build: Bump the non-rust-vmm group across 2 directories with 27 updates
Bumps the non-rust-vmm group with 14 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [bitflags](https://github.com/bitflags/bitflags) | `2.10.0` | `2.11.0` |
| [clap](https://github.com/clap-rs/clap) | `4.5.57` | `4.5.59` |
| [env_logger](https://github.com/rust-cli/env_logger) | `0.11.8` | `0.11.9` |
| [libc](https://github.com/rust-lang/libc) | `0.2.181` | `0.2.182` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.20.0` | `1.21.0` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.8.1` | `1.8.2` |
| [futures](https://github.com/rust-lang/futures-rs) | `0.3.31` | `0.3.32` |
| [async-executor](https://github.com/smol-rs/async-executor) | `1.13.3` | `1.14.0` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.55` | `1.2.56` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.19` | `0.2.20` |
| [syn](https://github.com/dtolnay/syn) | `2.0.114` | `2.0.116` |
| [toml_parser](https://github.com/toml-rs/toml) | `1.0.6+spec-1.1.0` | `1.0.9+spec-1.1.0` |
| [unicode-ident](https://github.com/dtolnay/unicode-ident) | `1.0.23` | `1.0.24` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.20` | `1.0.21` |

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

| Package | From | To |
| --- | --- | --- |
| [bitflags](https://github.com/bitflags/bitflags) | `2.10.0` | `2.11.0` |
| [clap](https://github.com/clap-rs/clap) | `4.5.57` | `4.5.59` |
| [libc](https://github.com/rust-lang/libc) | `0.2.181` | `0.2.182` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.20.0` | `1.21.0` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.8.1` | `1.8.2` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.55` | `1.2.56` |
| [futures-core](https://github.com/rust-lang/futures-rs) | `0.3.31` | `0.3.32` |
| [futures-sink](https://github.com/rust-lang/futures-rs) | `0.3.31` | `0.3.32` |
| [syn](https://github.com/dtolnay/syn) | `2.0.114` | `2.0.116` |
| [toml_parser](https://github.com/toml-rs/toml) | `1.0.6+spec-1.1.0` | `1.0.9+spec-1.1.0` |
| [unicode-ident](https://github.com/dtolnay/unicode-ident) | `1.0.23` | `1.0.24` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.20` | `1.0.21` |
| [libfuzzer-sys](https://github.com/rust-fuzz/libfuzzer) | `0.4.10` | `0.4.12` |



Updates `bitflags` from 2.10.0 to 2.11.0
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.10.0...2.11.0)

Updates `clap` from 4.5.57 to 4.5.59
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.57...clap_complete-v4.5.59)

Updates `env_logger` from 0.11.8 to 0.11.9
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.8...v0.11.9)

Updates `libc` from 0.2.181 to 0.2.182
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.182/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.181...0.2.182)

Updates `uuid` from 1.20.0 to 1.21.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.20.0...v1.21.0)

Updates `arc-swap` from 1.8.1 to 1.8.2
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.8.1...v1.8.2)

Updates `futures` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `async-executor` from 1.13.3 to 1.14.0
- [Release notes](https://github.com/smol-rs/async-executor/releases)
- [Changelog](https://github.com/smol-rs/async-executor/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/async-executor/compare/v1.13.3...v1.14.0)

Updates `cc` from 1.2.55 to 1.2.56
- [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.55...cc-v1.2.56)

Updates `clap_builder` from 4.5.57 to 4.5.59
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.57...v4.5.59)

Updates `clap_lex` from 0.7.7 to 1.0.0
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_lex-v0.7.7...clap_lex-v1.0.0)

Updates `env_filter` from 0.1.4 to 1.0.0
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/env_filter-v0.1.4...env_filter-v1.0.0)

Updates `futures-channel` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `futures-core` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `futures-executor` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `futures-io` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `futures-macro` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `futures-sink` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `futures-task` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `futures-util` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `jiff` from 0.2.19 to 0.2.20
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.19...jiff-static-0.2.20)

Updates `jiff-static` from 0.2.19 to 0.2.20
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.19...jiff-static-0.2.20)

Updates `syn` from 2.0.114 to 2.0.116
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.114...2.0.116)

Updates `toml_parser` from 1.0.6+spec-1.1.0 to 1.0.9+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.0.6...toml_parser-v1.0.9)

Updates `unicode-ident` from 1.0.23 to 1.0.24
- [Release notes](https://github.com/dtolnay/unicode-ident/releases)
- [Commits](https://github.com/dtolnay/unicode-ident/compare/1.0.23...1.0.24)

Updates `zmij` from 1.0.20 to 1.0.21
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.20...1.0.21)

Updates `bitflags` from 2.10.0 to 2.11.0
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.10.0...2.11.0)

Updates `clap` from 4.5.57 to 4.5.59
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.57...clap_complete-v4.5.59)

Updates `libc` from 0.2.181 to 0.2.182
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.182/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.181...0.2.182)

Updates `uuid` from 1.20.0 to 1.21.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.20.0...v1.21.0)

Updates `arc-swap` from 1.8.1 to 1.8.2
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.8.1...v1.8.2)

Updates `cc` from 1.2.55 to 1.2.56
- [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.55...cc-v1.2.56)

Updates `clap_builder` from 4.5.57 to 4.5.59
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.57...v4.5.59)

Updates `clap_lex` from 0.7.7 to 1.0.0
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_lex-v0.7.7...clap_lex-v1.0.0)

Updates `futures-core` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `futures-sink` from 0.3.31 to 0.3.32
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.31...0.3.32)

Updates `syn` from 2.0.114 to 2.0.116
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.114...2.0.116)

Updates `toml_parser` from 1.0.6+spec-1.1.0 to 1.0.9+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.0.6...toml_parser-v1.0.9)

Updates `unicode-ident` from 1.0.23 to 1.0.24
- [Release notes](https://github.com/dtolnay/unicode-ident/releases)
- [Commits](https://github.com/dtolnay/unicode-ident/compare/1.0.23...1.0.24)

Updates `zmij` from 1.0.20 to 1.0.21
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.20...1.0.21)

Updates `libfuzzer-sys` from 0.4.10 to 0.4.12
- [Changelog](https://github.com/rust-fuzz/libfuzzer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-fuzz/libfuzzer/commits)

---
updated-dependencies:
- dependency-name: bitflags
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.5.59
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: env_logger
  dependency-version: 0.11.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.182
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: futures
  dependency-version: 0.3.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: async-executor
  dependency-version: 1.14.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.56
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.59
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_lex
  dependency-version: 1.0.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: env_filter
  dependency-version: 1.0.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: futures-channel
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: futures-core
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: futures-executor
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: futures-io
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: futures-macro
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: futures-sink
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: futures-task
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: futures-util
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.20
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.20
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.116
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_parser
  dependency-version: 1.0.9+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: unicode-ident
  dependency-version: 1.0.24
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.21
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bitflags
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.5.59
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.182
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.8.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.56
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.59
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_lex
  dependency-version: 1.0.0
  dependency-type: indirect
  update-type: version-update:semver-major
  dependency-group: non-rust-vmm
- dependency-name: futures-core
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: futures-sink
  dependency-version: 0.3.32
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.116
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_parser
  dependency-version: 1.0.9+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: unicode-ident
  dependency-version: 1.0.24
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.21
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libfuzzer-sys
  dependency-version: 0.4.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-17 01:03:53 +00:00
dependabot[bot]
2a5c4f0e4d build: Bump crate-ci/typos from 1.43.4 to 1.43.5
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.4 to 1.43.5.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.43.4...v1.43.5)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.43.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-17 00:49:30 +00:00
Rob Bradford
5dd5659440 build: Switch to released version of acpi_tables
Replace git version with latest released version from crates.io

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-16 22:07:52 +00:00
Anatol Belski
16d6a16e5c performance-metrics: Add standalone qcow2 perf tests
Add performance tests for standalone qcow2 images without backing
files - uncompressed, zlib and zstd compressed. Each variant
includes single queue and multiqueue tests for sequential
read, random read and warmed up sequential read.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-16 11:50:23 +00:00
Anatol Belski
333db1acb3 performance-metrics: Add multi-queue overlay block I/O tests
Add multiqueue num_queues=4 performance tests for qcow2 overlay
images with both qcow2 and raw backing files - sequential read,
random read, and warm read variants.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-16 11:50:23 +00:00
Anatol Belski
ecb971a491 performance-metrics: Add backing_files=on for overlay tests
The backing_files option defaults to false, so qcow2 overlay
tests fail with MaxNestingDepthExceeded. Pass backing_files=on
when the test file is an overlay.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-15 12:31:32 +00:00
Anatol Belski
20f5cadb6f scripts: Fix run_metrics.sh to use sha1sums common file
The focal image checksums have been moved in the -common
sha1sums file. Use the correct file for metrics.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-15 12:31:32 +00:00
Demi Marie Obenour
ce93686ad2 block: do not allow guest to not negotiate VIRTIO_BLK_F_RO
Unlike most virtio feature bits, VIRTIO_BLK_F_RO is not optional.
It indicates that the host is refusing to permit write operations, and
the guest must not be allowed to override it.

However, the block device currently does not enforce this.  If the guest
does not negotiate VIRTIO_BLK_F_RO, the block device will think the
device is writable and forward write requests to the backend.

This is not a security problem right now because the backing device of a
read-only device is always opened read-only.  The kernel will thus
reject the write operations with EBADF.  If support is added for
receiving the backing device file descriptor via SCM_RIGHTS (#7704),
it will be possible to have a read-only block device backed by a
writable file descriptor.  This would make the bug a genuine security
vulnerability.

Fix the bug by explicitly checking if VIRTIO_BLK_F_RO was offered but
not negotiated.  In this case, log a warning and proceed as if the guest
did acknowledge the feature.  This always indicates a guest driver bug.

Fixes: #7697
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-14 09:55:42 +00:00
Demi Marie Obenour
80ea1fe62d block: Allow (and ignore) FLUSH requests on read-only devices
OVMF sends FLUSH requests to read-only virtio-block devices.  Refusing
these requests prevents OVMF from accessing the EFI System Partition and
therefore makes VMs unable to boot.  Accept these requests instead.
them.

Ignoring these requests is possible, but inconsistent with fsync(2)
which honors them.

Fixes: #7698
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-14 09:54:46 +00:00
Muminul Islam
ff4c8f7480 hypervisor: mshv: handle Special Debug Print VMG exit
Add handling for GHCB_INFO_SPECIAL_DBGPRINT VMG exit in the SEV-SNP
guest exit handler. This exit occurs when the guest sends debug print
requests through the GHCB interface.

Without this handler, SEV-SNP guests fail to boot when debug output
is triggered, such as when a debugger is attached to the guest image.

The handler acknowledges the exit without printing to avoid performance
degradation from frequent debug print requests.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-13 22:22:00 +00:00
Leander Kohler
30c86d5999 vmm: fix --net fd helptext for list syntax
The --net help text documented fd as fd=<fd1,fd2...>, but
comma-separated FD lists in option values must be bracketed to avoid
top-level option splitting.

Update NetConfig::SYNTAX to use fd=<[fd1,fd2,...]>, matching parser
behavior and existing net parsing tests:
`cargo test -p vmm test_net_parsing`

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-02-13 20:58:30 +00:00
Leander Kohler
adfcd17bfc vmm: Include invalid UUID string in ParseUuid err
On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
2026-02-13 20:58:30 +00:00
Anatol Belski
8e51c57aea tests: Add integration tests for virtio-blk DISCARD operations
Add comprehensive integration tests for DISCARD and WRITE_ZEROES:

Multiqueue stress tests verify concurrent operations across queues,
testing scattered writes with simultaneous fstrim, and write/discard
races that stress refcount table locking.

Format specific tests verify QCOW2 deallocates clusters after DISCARD,
raw files create holes using fallocate, and unsupported formats VHD
and VHDX correctly reject DISCARD requests.

Tests for sparse=off verify raw files preallocate full disk size and
QCOW2 uses zero flag instead of deallocating clusters.

Add helper functions to verify sparse files, count QCOW2 zero flagged
regions using qemu-img map, and verify guest reads zeros from
discarded regions.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Anatol Belski
4676fdb494 block: Add unit tests for DISCARD zero flag
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>
2026-02-13 18:44:30 +00:00
Anatol Belski
45b115aeb0 block: raw: Implement punch_hole and write_zeroes
Implement punch_hole() and write_zeroes() for raw file backends using
io_uring and fallocate.

punch_hole() uses FALLOC_FL_PUNCH_HOLE to deallocate storage.
write_zeroes() uses FALLOC_FL_ZERO_RANGE to write zeros efficiently.

Both use FALLOC_FL_KEEP_SIZE to maintain file size.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Anatol Belski
6c94975c80 block: qcow: Implement punch_hole and write_zeroes for QcowSync
Implement punch_hole and write_zeroes for QcowSync backend by
delegating to QcowFile::punch_hole which triggers cluster
deallocation. write_zeroes delegates to punch_hole as unallocated
clusters read as zeros in QCOW2.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Anatol Belski
81c075b317 block: Add virtio-blk DISCARD and WRITE_ZEROES support
Add VIRTIO_BLK_T_DISCARD and VIRTIO_BLK_T_WRITE_ZEROES request types.
Parse discard/write_zeroes descriptors (sector, num_sectors, flags),
convert to byte offsets, and call punch_hole/write_zeroes on the disk
backend. Mark as unsupported in sync mode.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Anatol Belski
e913d0d9cb block: qcow: Implement DISCARD with sparse aware deallocation
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>
2026-02-13 18:44:30 +00:00
Anatol Belski
49a30cbbaf block: raw: Implement disk preallocation for sparse=false
When sparse=false is configured, preallocate the entire raw disk file
at startup using fallocate(). This provides space reservation and
reduces fragmentation.

Only applies to raw disks. QCOW2/VHD/VHDX formats manage their own
allocation.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Anatol Belski
0a287793df block: qcow: Thread sparse configuration to QCOW2 constructors
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>
2026-02-13 18:44:30 +00:00
Anatol Belski
46e6ecddfe block: Add supports_zero_flag trait method
Add supports_zero_flag() to DiskFile trait to indicate whether a disk
format can mark clusters/blocks as reading zeros without deallocating
storage.

QCOW2 supports this via the zero flag in L2 entries. VHDX also has
PAYLOAD_BLOCK_ZERO state for this, though it's not yet implemented in
cloud-hypervisor.

This enables DISCARD to be advertised even with sparse=false for formats
with zero-flag support, since they can mark regions as zeros (keeps
storage allocated) instead of requiring full deallocation.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Anatol Belski
7dfcbff309 vmm: config: Add sparse flag to control disk space allocation
Add sparse boolean configuration option to DiskConfig with a default
value of true to control disk space allocation behavior.

When sparse is true, the disk uses sparse allocation where deallocated
blocks are returned to the filesystem, and the DISCARD feature is
advertised to the guest.

When sparse is false, disk space is kept fully allocated and DISCARD
is not advertised.

WRITE_ZEROES is always advertised when the backend supports it,
regardless of the sparse setting.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Anatol Belski
7f4b56b217 block: Add sparse operations capability query
Add capability query to DiskFile trait to check backend
support for sparse operations (punch hole, write zeroes,
discard). Only advertise VIRTIO_BLK_F_DISCARD and
VIRTIO_BLK_F_WRITE_ZEROES when the backend supports these
operations.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Anatol Belski
d5dad48618 block: Add sparse capability detection
Add functions to probe whether a file or block device actually
supports PUNCH_HOLE and ZERO_RANGE operations at runtime. The
probe is performed at file open time by testing the operations
at EOF with a zero-length range, which is a safe no-op.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Anatol Belski
7095605d84 block: Add punch_hole and write_zeroes to AsyncIo trait
Add punch_hole() and write_zeroes() methods to the AsyncIo trait
with stub implementations for all backends. These will be used to
support DISCARD and WRITE_ZEROES operations.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-13 18:44:30 +00:00
Muminul Islam
41b23229a5 vmm: refactor VM initialization into modular helper methods
Decompose the monolithic `new_from_memory_manager` function into
smaller, focused helper methods to improve code readability,
maintainability, and testability.

Changes:
- Extract `should_force_iommu()` to determine IOMMU requirements for
  confidential computing (TDX/SEV-SNP)
- Extract `should_stop_on_boot()` to check debug pause configuration
- Extract `create_cpu_manager()` to encapsulate CPU manager creation
  and CPUID population
- Extract `init_tdx_if_enabled()` for TDX-specific VM initialization
- Extract `create_device_manager()` to encapsulate device manager setup
- Extract `hypervisor_specific_init()` to orchestrate initialization
  sequences for different hypervisors (KVM, MSHV, SEV-SNP)
- Extract `init_sev_snp()` for SEV-SNP confidential VM setup
- Extract `init_mshv()` for MSHV hypervisor initialization
- Extract `init_kvm()` for KVM hypervisor initialization
- Extract `create_fw_cfg_if_enabled()` for fw_cfg device creation

This refactoring replaces complex nested `cfg_if!` blocks with cleaner
conditional method calls, providing clear separation between hypervisor-
specific initialization paths while preserving existing functionality.

No functional changes intended.
Issue: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7598

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-13 10:18:50 +00:00
Philipp Schuster
03ef7d1991 vmm: remove lock for VmState
The lock doesn't make any sense. There is no shared ownership. All
accesses are already synchronized by accesses on a higher level.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-02-13 09:46:13 +00:00
Saravanan D
9babad0114 ci: fix lychee link-checker failures
Update .lychee.toml to exclude the following patterns:
- ARM domains (developer.arm.com, infocenter.arm.com) which return 403
  Forbidden due to anti-bot protections in CI.
- Local TCP addresses (192.168.1.10) which are unsupported by the
  link-checker tool.
- The .lychee.toml file itself, to prevent the tool from recursively
  checking its own regex exclusion patterns as valid URLs.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-12 22:54:54 +00:00
Saravanan D
3af9ac78a9 docs: Document Generic Initiator NUMA support
Document device_id parameter in NumaConfig, automatic
guest_numa_id assignment, default NUMA distances and
restrictions on Generoc Initiator NUMA nodes

Add numa configuration examples with GPU device and distance
relationships.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-12 22:54:54 +00:00
Saravanan D
18768e9bf8 tests: Add integration test for Generic Initiator
Add test_guest_numa_generic_initiator to validate ACPI Generic
Initiator Affinity (SRAT Type 5) support for VFIO device.

The test verifies the following :
- Guest VM boots with a VFIO device associated with a {cpu,
  memort}-less NUMA node
- Guest Kernel correctly detects Generic Initiator through
  ACPI tables SRAT, SLIT
- NUMA topology in the guest includes the device-only node
with correct distances

Invoked via :
./scripts/dev_cli.sh tests --integration -- --hypervisor kvm \
--test-filter test_guest_numa_generic_initiator

The test requires a real VFIO device bound to vfio-pci driver and
skips gracefully if hardware is unavailable.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-12 22:54:54 +00:00
Saravanan D
df67c3690e arch: Handle Generic Initiator in ARM64 FDT
Update FDT generation to skip NUMA properties when Generic Initiator
nodes are present, preventing conflicts between FDT and ACPI NUMA
information. FDT cannot represent Generic Initiator nodes, so ACPI
(via SRAT Type 5) becomes the authoritative source for the entire
NUMA topology when Generic Initiators exist.

Skip FDT numa-node-id properties in CPU and memory nodes
when Generic Initiator is present

Distance map bug fix : iterate over actual NUMA node IDs instead
of 0..len()

Use distance symmetry to derive distance when forward config is
missing

Default to distance cost 20 when neither direction specified

Only create memory nodes if NUMA node has memory region

Added unit tests

ARM64 boot protocol:
https://docs.kernel.org/arch/arm64/booting.html

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-12 22:54:54 +00:00
Saravanan D
dc0c306dd9 vmm: Add ACPI Generic Initiator support
Support ACPI Generic Initiator Affinity to associate
PCI devices with NUMA proximity domains

Add GenericInitiatorAffinity struct

Add from_pci_bdf() to encode PCI Segment:Bus:Device.Function

Add from_acpi_device() for ACPI device handles (future use)

Generate SRAT Type 5 entries for nodes with device_id

Improve create_slit_table() to check distance symmetry when
forward distance is missing

Track device ID to BDF mappings in DeviceManager

Includes comprehensive unit tests

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-12 22:54:54 +00:00
Saravanan D
fa43548975 vmm: Add validation for Generic Initiator NUMA
Validate device_id in numa config is mutually
exclusive with cpus and memory_zones

Add NumaConfig::validate() and modify NumaConfig::parse()

Add ValidationError::InvalidNumaConfig for detailed error
messages

Include unit tests covering valid and invalid configs

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-12 22:54:54 +00:00
Saravanan D
6d4827b5ff vmm: Add device_id field to NUMA configuration
Add an optional device_id string field to NumaConfig for identifying
PCI devices associated with a NUMA node. This is used by the Generic
Initiator support to map devices to their proximity domain.

Update OpenAPI spec (cloud-hypervisor.yaml) to include the
new device_id field in the NumaConfig schema.

The device_id is optional and parsed from the --numa parameter:
  --numa "device_id=<device_id>,distances=[...],..."

The optional field is accepted but not used.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-12 22:54:54 +00:00
Saravanan D
231bbe2d5d vmm: Enforce guest_numa_id on NUMA nodes
The documentation says guest_numa_id is required to be unique and
therefore the parser() giving default value for non-existing
guest_numa_id with .unwrap_or(0) is dangerous.

Return a validation error if guest_numa_id is not provided instead
of silently defaulting to 0.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-02-12 22:54:54 +00:00
Anatol Belski
9ba9c0819a tests: Add QCOW2 disk resize integration test
Verify live resize of QCOW2 disks works via the API, including
resizing that requires L1 table growth.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-12 22:47:00 +00:00
Anatol Belski
99493c728e block: qcow: Add resize unit tests
- No-op resize when size unchanged
- Growing with L1 table expansion
- Shrink attempts return error
- Resize with backing file returns error

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-12 22:47:00 +00:00
Anatol Belski
629c117ff3 block: qcow: Implement live resize with L1 table growth
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>
2026-02-12 22:47:00 +00:00
Anirudh Rayabharam
caa362c31f hypervisor: mshv: stub implementation for save_data_tables()
Provide a stub implementation for save_data_tables() to unblock pause
functionality. Without this, pausing a VM causes Cloud Hypervisor to
panic due to the unimplemented!() macro. This unblocks the
test_api_http_pause_resume testcase. We don't need to save any state
just to pause and resume.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-02-11 15:57:21 +00:00
Philipp Schuster
2882ccd00a vmm: config validation: add more context to errors
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-02-11 10:04:14 +00:00
Demi Marie Obenour
92b58ba94a virtio-devices: Do not close an unowned FD
Instead of closing a file descriptor that belongs to the vhost-user
frontend, drop the vu_common_ctrl::VhostUserHandle and the
vhost::vhost_user::Frontend it contains.  This causes the destructor to
drop the file descriptor.

This breaks the last DPDK test, so disable it.  See #7689.

Fixes: #7163
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-11 09:53:10 +00:00
Damian Barabonkov
dde5f6ef38 vmm: Fix MMIO region removal during VFIO device hot-unplug
When a VFIO device with multiple MMIO regions is hot-unplugged, each
region must be individually matched and removed from the DeviceManager's
mmio_regions list. Compare per-region rather than building an aggregate
across all regions, which would never match any individual entry.

Also remove the now-unused HashSet import.

Signed-off-by: Damian Barabonkov <dbctl@pm.me>
2026-02-11 00:03:01 +00:00
Damian Barabonkov
638848fcff pci: Accept MmioRegion reference in has_matching_slots()
Change has_matching_slots() to compare two MmioRegion instances
directly rather than requiring callers to construct an intermediate
HashSet of slot numbers. Remove the now-unused
user_memory_region_slots() method and HashSet import.

Signed-off-by: Damian Barabonkov <dbctl@pm.me>
2026-02-11 00:03:01 +00:00
Changyuan Lyu
c2add07476 vmm: device_manager: avoid deep cloning device configs
Replace `clone()` with `take()` when retrieving device configurations
from `DeviceManager.config`.

This avoids unnecessarily copying the device configuration lists (e.g.,
`disks`, `net`, `fs`) when they are being processed and subsequently
moved out of the configuration. This optimization improves performance
by reducing memory allocations and cloning overhead.

Signed-off-by: Changyuan Lyu <changyuanl@google.com>
2026-02-10 23:35:54 +00:00
Wei Liu
bf6f0f8352 virtio-devices: vsock: Accept multi-descriptor TX packets
Since kernel commit 6693731487a8 ("vsock/virtio: Allocate nonlinear SKBs
for handling large transmit buffers"), a large vsock packet can be split
into multiple descriptors.

If we encounter such TX packets, pull the content into an owned buffer.

Fixes: #7672
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-02-10 22:00:27 +00:00
Muminul Islam
97c5d837ab scripts: allow cloud-init script to accept a custom output file
When running manual tests locally, it is sometimes necessary to
generate a cloud-init file at a custom path instead of defaulting
to /tmp. This is useful for developers and higher-level management
layers where files in /tmp may be cleaned up automatically.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-10 21:50:29 +00:00
Rob Bradford
509832298b vmm: Add option to control backing files
Backing files (e.g. for QCOW2) interact badly with landlock since they
are not obvious from the initial VM configuration. Only enable their use
with an explicit option.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-10 17:41:42 +00:00
dependabot[bot]
a702bf1d10 build: Bump the non-rust-vmm group across 2 directories with 19 updates
Includes fix for rand build error (need to use trait).

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

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.100` | `1.0.101` |
| [clap](https://github.com/clap-rs/clap) | `4.5.56` | `4.5.57` |
| [libc](https://github.com/rust-lang/libc) | `0.2.180` | `0.2.181` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.37` | `0.8.39` |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.1.8` | `1.1.9` |
| [getrandom](https://github.com/rust-random/getrandom) | `0.4.0` | `0.4.1` |
| [rand](https://github.com/rust-random/rand) | `0.9.2` | `0.10.0` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.18` | `0.2.19` |
| [memchr](https://github.com/BurntSushi/memchr) | `2.7.6` | `2.8.0` |
| [regex](https://github.com/rust-lang/regex) | `1.12.2` | `1.12.3` |
| [regex-automata](https://github.com/rust-lang/regex) | `0.4.13` | `0.4.14` |
| [regex-syntax](https://github.com/rust-lang/regex) | `0.8.8` | `0.8.9` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.24.0` | `3.25.0` |
| [unicode-ident](https://github.com/dtolnay/unicode-ident) | `1.0.22` | `1.0.23` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.19` | `1.0.20` |

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

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.100` | `1.0.101` |
| [clap](https://github.com/clap-rs/clap) | `4.5.56` | `4.5.57` |
| [libc](https://github.com/rust-lang/libc) | `0.2.180` | `0.2.181` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.37` | `0.8.39` |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.1.8` | `1.1.9` |
| [memchr](https://github.com/BurntSushi/memchr) | `2.7.6` | `2.8.0` |
| [unicode-ident](https://github.com/dtolnay/unicode-ident) | `1.0.22` | `1.0.23` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.19` | `1.0.20` |

Updates `anyhow` from 1.0.100 to 1.0.101
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.100...1.0.101)

Updates `clap` from 4.5.56 to 4.5.57
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.56...clap_complete-v4.5.57)

Updates `libc` from 0.2.180 to 0.2.181
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.181/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.180...0.2.181)

Updates `zerocopy` from 0.8.37 to 0.8.39
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.37...v0.8.39)

Updates `flate2` from 1.1.8 to 1.1.9
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.1.8...1.1.9)

Updates `getrandom` from 0.4.0 to 0.4.1
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.4.0...v0.4.1)

Updates `rand` from 0.9.2 to 0.10.0
- [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/rand_core-0.9.2...0.10.0)

Updates `clap_builder` from 4.5.56 to 4.5.57
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.56...v4.5.57)

Updates `jiff` from 0.2.18 to 0.2.19
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.18...jiff-static-0.2.19)

Updates `jiff-static` from 0.2.18 to 0.2.19
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.18...jiff-static-0.2.19)

Updates `memchr` from 2.7.6 to 2.8.0
- [Commits](https://github.com/BurntSushi/memchr/compare/2.7.6...2.8.0)

Updates `regex` from 1.12.2 to 1.12.3
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.2...1.12.3)

Updates `regex-automata` from 0.4.13 to 0.4.14
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-automata-0.4.13...regex-automata-0.4.14)

Updates `regex-syntax` from 0.8.8 to 0.8.9
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-syntax-0.8.8...regex-syntax-0.8.9)

Updates `tempfile` from 3.24.0 to 3.25.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits)

Updates `unicode-ident` from 1.0.22 to 1.0.23
- [Release notes](https://github.com/dtolnay/unicode-ident/releases)
- [Commits](https://github.com/dtolnay/unicode-ident/compare/1.0.22...1.0.23)

Updates `zerocopy-derive` from 0.8.37 to 0.8.39
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.37...v0.8.39)

Updates `zmij` from 1.0.19 to 1.0.20
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.19...1.0.20)

Updates `anyhow` from 1.0.100 to 1.0.101
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.100...1.0.101)

Updates `clap` from 4.5.56 to 4.5.57
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.56...clap_complete-v4.5.57)

Updates `libc` from 0.2.180 to 0.2.181
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.181/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.180...0.2.181)

Updates `zerocopy` from 0.8.37 to 0.8.39
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.37...v0.8.39)

Updates `flate2` from 1.1.8 to 1.1.9
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.1.8...1.1.9)

Updates `clap_builder` from 4.5.56 to 4.5.57
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.56...v4.5.57)

Updates `hashbrown` from 0.16.1 to 0.15.5
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.16.1...v0.15.5)

Updates `memchr` from 2.7.6 to 2.8.0
- [Commits](https://github.com/BurntSushi/memchr/compare/2.7.6...2.8.0)

Updates `unicode-ident` from 1.0.22 to 1.0.23
- [Release notes](https://github.com/dtolnay/unicode-ident/releases)
- [Commits](https://github.com/dtolnay/unicode-ident/compare/1.0.22...1.0.23)

Updates `zerocopy-derive` from 0.8.37 to 0.8.39
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.37...v0.8.39)

Updates `zmij` from 1.0.19 to 1.0.20
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.19...1.0.20)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.101
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.5.57
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.181
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.39
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: flate2
  dependency-version: 1.1.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: getrandom
  dependency-version: 0.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.57
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.19
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.19
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: memchr
  dependency-version: 2.8.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: regex
  dependency-version: 1.12.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: regex-automata
  dependency-version: 0.4.14
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: regex-syntax
  dependency-version: 0.8.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: tempfile
  dependency-version: 3.25.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: unicode-ident
  dependency-version: 1.0.23
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.39
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.20
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: anyhow
  dependency-version: 1.0.101
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.5.57
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.181
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.39
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: flate2
  dependency-version: 1.1.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.57
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: hashbrown
  dependency-version: 0.15.5
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: memchr
  dependency-version: 2.8.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: unicode-ident
  dependency-version: 1.0.23
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.39
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.20
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-02-10 15:26:04 +00:00
Anatol Belski
279344800e block: qcow: Add test for reads beyond backing file size
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>
2026-02-10 08:39:57 +00:00
Anatol Belski
c569a4cbd4 block: qcow: Return zeros for reads beyond backing file size
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>
2026-02-10 08:39:57 +00:00
Wei Liu
2c49f4f4f3 hypervisor: Add an unreachable arm to get/set_x86_64_reg
This is useful when neither kvm nor mshv is defined.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2026-02-10 08:33:43 +00:00
dependabot[bot]
7157e97083 build: Bump crate-ci/typos from 1.43.3 to 1.43.4
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.3 to 1.43.4.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.43.3...v1.43.4)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.43.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 02:51:10 +00:00
Anatol Belski
a00189cf72 hypervisor: vmm: mshv: Enable SMT for guests with threads_per_core > 1
Set HV_PARTITION_CREATION_FLAG_SMT_ENABLED_GUEST when the guest
topology has more than one thread per core. This allows the
hypervisor to schedule guest VPs correctly on SMT-enabled hosts.

Without this flag, the hypervisor schedules guest VPs incorrectly,
causing SMT unusable.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-07 09:55:17 +00:00
Anatol Belski
e3a2bf0870 build: Update mshv crates to 0.6.7
Release notes: https://github.com/rust-vmm/mshv/pull/307

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-07 09:55:17 +00:00
dependabot[bot]
7314a77d43 build: Bump crate-ci/typos from 1.43.2 to 1.43.3
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.2 to 1.43.3.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.43.2...v1.43.3)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.43.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-07 00:45:27 +00:00
Champ-Goblem
1e0eba60af vmm: always try THP for VM RAM
The kernel allows madvise on shared memory if
/sys/kernel/mm/transparent_hugepage/shmem_enabled is set.
Always try and configure THP via madvise when
the user requests THP be enabled.
If this fails, only a warning log is emitted and THP won't be enabled.

Signed-off-by: Champ-Goblem <cameron@northflank.com>
2026-02-06 18:40:26 +00:00
Rowen-Ye
2c2f5d2431 vmm: open backing file read-only when not shared
When restoring from snapshot with shared=false, write access to the
backing file is not required. Opening it read-only allows restore to
succeed on read-only media and overlay lower layers while preserving
MAP_PRIVATE semantics.

Signed-off-by: Rowen-Ye <rowenye1@gmail.com>
2026-02-06 16:06:48 +00:00
Muminul Islam
258f826027 tests: Add initial support of CVM test on MSHV
This patch adds the skeleton of the CVM test
support and modify existing scripts and test framework
to enable such scenario. Split the sha1sum to support both
regular and CVM guest. Add one test case for CVM. Will further
add more test cases.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-06 06:37:26 +00:00
Muminul Islam
f391a37a35 scripts: add common image download code to utility script
X64_64 image download steps is being used for both
regular and CVM guest. Keeping the steps withing a function
in the test-util.sh

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-06 06:37:26 +00:00
Muminul Islam
6042eb969e tests: remove timeout argument
Now Guest struct has an option to set timeout.
No need to pass timeout while booting the guest.
If no timeout is set, default is used.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-06 06:37:26 +00:00
Muminul Islam
12f66b7ddc tests: Add option to pass guest for tests
Modify Guest struct to keep some test specific
data so that test cases could be shared between
regular guest and CVM.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-02-06 06:37:26 +00:00
dependabot[bot]
96f663b5f9 build: Bump crate-ci/typos from 1.43.1 to 1.43.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.1 to 1.43.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.43.1...v1.43.2)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.43.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 00:46:42 +00:00
Demi Marie Obenour
093a8497d0 scripts: Upgrade virtiofsd to 1.13.3
Testing generic vhost-user devices will require virtiofsd to support the
--tag option, which v1.8.0 does not support.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-02-04 17:29:48 +00:00
Anatol Belski
8c168d928f block: qcow: Add SyncingHeader error variant for fsync operations
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>
2026-02-04 10:33:22 +00:00
dependabot[bot]
7667e8c0bc build: Bump crate-ci/typos from 1.43.0 to 1.43.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.0 to 1.43.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.43.0...v1.43.1)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.43.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-04 01:25:57 +00:00
Anatol Belski
9bc367a27b block: qcow: Use Arc<Mutex<>> for thread safe multiqueue access
Wrap QcowFile in Arc<Mutex<>> to ensure thread safety when multiple
virtio queues access the same QCOW2 image concurrently.

Previously, each queue received its own QcowSync instance via
new_async_io() that shared the underlying QcowFile through Clone.
However, cloned QcowFile instances share internal mutable state
(L2 cache, reference counts, file seek position) without
synchronization, leading to data corruption under concurrent I/O.

This change serializes all QCOW2 operations through a mutex, which
ensures correctness at the cost of parallelism. A more performant
solution would require separating metadata locking from actual I/O
operations, tracked in #7560.

Related: #7560

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-03 22:43:38 +00:00
Anatol Belski
51662159c0 tests: qcow: Add multiqueue stress tests
Add stress tests for QCOW2 with >=8 virtio queues to verify
thread safety of multiqueue concurrent disk access:

- parallel dd writes
- 4 readers + 4 writers mixed I/O
- overlay with backing file
- random 4K writes
- parallel small writes + fsync
- mkdir/touch/rm/rename metadata operations

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-03 22:43:38 +00:00
Philipp Schuster
e690d258cc ci: reduce CI load by refining workflow concurrency groups
TL;DR: Would reduce CI pressure by cancelling more "unnecessary" runs
       but I can't verify without running a merge queue.

A common development pattern is to push a change and then immediately
check CI results. Follow-up fix pushes are quite common, which leads to
multiple CI runs being queued for the same pull request.

In Cloud Hypervisor, the size and cost of the CI matrix means that
several consecutive pushes (for example 3-4 in a short time) put
significant pressure on CI runners and noticeably increase feedback
latency.

In practice, concurrency handling is especially tricky for the merge
queue. From personal experience: If one does not take special care, CI
runs triggered by a `merge_group` can cancel each other, as in a merge
queue there are two runs for each job by default: one for the normal PR
and one for the merge commit. This is easy to run into, also because the
available documentation and best practices for this feature are not very
good.

At the same time, our workflows do not run on `push` events, but only
on `pull_request` and `merge_group`. Because of this, using
`${{ github.ref }}` alone as a concurrency key is not very meaningful,
and in practice only few runs are actually cancelled for successive PR
updates. Therefore, we should improve the usage of this feature.

This change tries to improve the situation by refining the concurrency
group key. The goal is to keep cancellation for multiple PR pushes,
while at the same time preventing unintended cancellations in the merge
queue by separating `merge_group` runs from regular PR runs.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-02-03 17:19:09 +00:00
Philipp Schuster
591aeb8ed8 misc: fix spellcheck CI step (caused by typos v1.43)
Unfortunately, we merged a typos bump [0] with failing CI.

[0] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/7654

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
2026-02-03 16:27:46 +00:00
stevenhorsman
512c3ea58d api_client: Add license info to api_client crate
In kata-containers we use the api_client crate, but it's currently
failing our cargo deny check due to missing license, and there aren't
any license files within the crate, so I haven't found a good way to
work around this.

Alternatively I'd be happy to add the license to the workspace crate
and then reference it here, but that seems to clash with the direction
of the project in #7525.

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-02-03 15:31:03 +00:00
Anatol Belski
4ba0db5948 block: qcow: Add unit tests for autoclear features
- Autoclear bits cleared when opening for write
- Autoclear bits preserved when opening readonly
- V2 images not affected

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-02-03 14:39:18 +00:00
Anatol Belski
4545fe113e block: qcow: Clear autoclear features on writable open
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>
2026-02-03 14:39:18 +00:00
dependabot[bot]
57fd672db6 build: Bump the non-rust-vmm group across 2 directories with 14 updates
Bumps the non-rust-vmm group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.5.54` | `4.5.56` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.34` | `0.8.37` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.8.0` | `1.8.1` |
| [getrandom](https://github.com/rust-random/getrandom) | `0.3.4` | `0.4.0` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.54` | `1.2.55` |
| [openssl-src](https://github.com/alexcrichton/openssl-src-rs) | `300.5.4+3.5.4` | `300.5.5+3.5.5` |
| [portable-atomic](https://github.com/taiki-e/portable-atomic) | `1.13.0` | `1.13.1` |
| [portable-atomic-util](https://github.com/taiki-e/portable-atomic) | `0.2.4` | `0.2.5` |
| [slab](https://github.com/tokio-rs/slab) | `0.4.11` | `0.4.12` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.17` | `1.0.19` |

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

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.5.54` | `4.5.56` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.34` | `0.8.37` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.8.0` | `1.8.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.54` | `1.2.55` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.17` | `1.0.19` |



Updates `clap` from 4.5.54 to 4.5.56
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.54...clap_complete-v4.5.56)

Updates `zerocopy` from 0.8.34 to 0.8.37
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.34...v0.8.37)

Updates `arc-swap` from 1.8.0 to 1.8.1
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.8.0...v1.8.1)

Updates `getrandom` from 0.3.4 to 0.4.0
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.3.4...v0.4.0)

Updates `cc` from 1.2.54 to 1.2.55
- [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.54...cc-v1.2.55)

Updates `clap_builder` from 4.5.54 to 4.5.56
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.54...v4.5.56)

Updates `find-msvc-tools` from 0.1.8 to 0.1.9
- [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/find-msvc-tools-v0.1.8...find-msvc-tools-v0.1.9)

Updates `hashbrown` from 0.16.1 to 0.15.5
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.16.1...v0.15.5)

Updates `openssl-src` from 300.5.4+3.5.4 to 300.5.5+3.5.5
- [Release notes](https://github.com/alexcrichton/openssl-src-rs/releases)
- [Commits](https://github.com/alexcrichton/openssl-src-rs/commits)

Updates `portable-atomic` from 1.13.0 to 1.13.1
- [Release notes](https://github.com/taiki-e/portable-atomic/releases)
- [Changelog](https://github.com/taiki-e/portable-atomic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/portable-atomic/compare/v1.13.0...v1.13.1)

Updates `portable-atomic-util` from 0.2.4 to 0.2.5
- [Release notes](https://github.com/taiki-e/portable-atomic/releases)
- [Changelog](https://github.com/taiki-e/portable-atomic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/portable-atomic/compare/portable-atomic-util-0.2.4...portable-atomic-util-0.2.5)

Updates `slab` from 0.4.11 to 0.4.12
- [Release notes](https://github.com/tokio-rs/slab/releases)
- [Changelog](https://github.com/tokio-rs/slab/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/slab/compare/v0.4.11...v0.4.12)

Updates `zerocopy-derive` from 0.8.34 to 0.8.37
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.34...v0.8.37)

Updates `zmij` from 1.0.17 to 1.0.19
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.17...1.0.19)

Updates `clap` from 4.5.54 to 4.5.56
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.54...clap_complete-v4.5.56)

Updates `zerocopy` from 0.8.34 to 0.8.37
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.34...v0.8.37)

Updates `arc-swap` from 1.8.0 to 1.8.1
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.8.0...v1.8.1)

Updates `cc` from 1.2.54 to 1.2.55
- [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.54...cc-v1.2.55)

Updates `clap_builder` from 4.5.54 to 4.5.56
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.54...v4.5.56)

Updates `find-msvc-tools` from 0.1.8 to 0.1.9
- [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/find-msvc-tools-v0.1.8...find-msvc-tools-v0.1.9)

Updates `zerocopy-derive` from 0.8.34 to 0.8.37
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.34...v0.8.37)

Updates `zmij` from 1.0.17 to 1.0.19
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.17...1.0.19)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.37
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: getrandom
  dependency-version: 0.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.55
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.56
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: find-msvc-tools
  dependency-version: 0.1.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: hashbrown
  dependency-version: 0.15.5
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: openssl-src
  dependency-version: 300.5.5+3.5.5
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: portable-atomic
  dependency-version: 1.13.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: portable-atomic-util
  dependency-version: 0.2.5
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: slab
  dependency-version: 0.4.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.37
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.19
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.5.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.37
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.8.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.55
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.56
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: find-msvc-tools
  dependency-version: 0.1.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.37
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.19
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 05:49:17 +00:00
dependabot[bot]
009af915bb build: Bump crate-ci/typos from 1.42.3 to 1.43.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.3 to 1.43.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.42.3...v1.43.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 05:35:45 +00:00
Rob Bradford
01bd6b8b1a virtio-devices: vhost-user: net: Use default vhost-user virtio features
This adds some missing features that are useful. In particular it adds
VIRTIO_F_RING_INDIRECT_DESC which gives a performance improvement.

Signed-off-by: Rob Bradford <rbradford@meta.com>
Reported-by: Daniel Farina <daniel@ubicloud.com>
2026-02-02 11:52:52 +00:00
Zhibin Li
28686bba46 vmm: fix rsdp_addr assertion for TDX
TDX builds its own ACPI tables in `create_acpi_tables_tdx` so it will
return None in the standard `create_acpi_tables` function and the
assertion for `rsdp_addr` will fail.

Signed-off-by: Zhibin Li <banlu.lzb@antgroup.com>
2026-01-29 16:23:22 +00:00
Anatol Belski
2da05d4258 block: qcow: Extend corrupt bit unit tests
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>
2026-01-29 09:28:51 +00:00
Anirudh Rayabharam
323b8230a3 tests: fix tests that expect ITS for mshv arm64
MSHV doesn't present an ITS to guests. So, /proc/interrupts would never
have "ITS-PCI-MSIX".

Instead, a Gicv2m frame is presented to guests. So expect
"GICv2m-PCI-MSIX" in testcases.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-01-28 19:18:57 +00:00
Anatol Belski
edaeaed5f7 tests: qcow: Add corrupt bit integration tests
Add integration tests for QCOW2 corrupt bit handling. Verify that
images with the corrupt bit set are rejected for writable access but
allowed for read-only access with a warning.

Helper functions are added to read and modify the corrupt flag in the
QCOW2 v3 header.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-28 15:30:33 +00:00
Anatol Belski
9baa904a5c block: qcow: Add offset alignment checks for corruption detection
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>
2026-01-28 15:30:33 +00:00
Anatol Belski
2d86fc8422 block: qcow: Set corrupt bit on known inconsistencies
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>
2026-01-28 15:30:33 +00:00
Anatol Belski
c2fcb9bac9 block: qcow: Add unit tests for corrupt bit
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>
2026-01-28 15:30:33 +00:00
Anatol Belski
771ab1d5a3 block: qcow: Add corrupt bit support for QCOW2 v3 images
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>
2026-01-28 15:30:33 +00:00
dependabot[bot]
111f37f926 build: Bump crate-ci/typos from 1.42.2 to 1.42.3
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.2 to 1.42.3.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.42.2...v1.42.3)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.42.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-28 00:44:37 +00:00
Thomas Leroy
929df76e1a virtio-devices: copy VSock header from guest
VsockPacket::hdr holds a raw pointer to the address of the VSock packet
header, which is in guest memory. It opens the door to double-fetch
(or TOCTOU) race conditions. Therefore, VSockPacket::hdr content can't
be trusted since it can be arbitrarily changed by the guest, at any
time.

To mitigate this, we can copy the header content to an array in VMM's
memory that the guest can't modify.

Signed-off-by: Thomas Leroy <thomas.leroy.mp@gmail.com>
2026-01-27 19:39:07 +00:00
Anatol Belski
87e8ac3f1f block: qcow: Use BeUint for header field I/O
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>
2026-01-27 18:47:39 +00:00
Anatol Belski
8cdc3b53c0 block: qcow: Extend BeUint trait with read_be() method
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>
2026-01-27 18:47:39 +00:00
dependabot[bot]
79953fece0 build: Bump the non-rust-vmm group across 2 directories with 9 updates
Bumps the non-rust-vmm group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [signal-hook](https://github.com/vorner/signal-hook) | `0.4.1` | `0.4.3` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.19.0` | `1.20.0` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.33` | `0.8.34` |
| [gdbstub](https://github.com/daniel5151/gdbstub) | `0.7.8` | `0.7.9` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.53` | `1.2.54` |
| [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.105` | `1.0.106` |
| [quote](https://github.com/dtolnay/quote) | `1.0.43` | `1.0.44` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.15` | `1.0.17` |

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

| Package | From | To |
| --- | --- | --- |
| [signal-hook](https://github.com/vorner/signal-hook) | `0.4.1` | `0.4.3` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.19.0` | `1.20.0` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.33` | `0.8.34` |
| [gdbstub](https://github.com/daniel5151/gdbstub) | `0.7.8` | `0.7.9` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.53` | `1.2.54` |
| [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.105` | `1.0.106` |
| [quote](https://github.com/dtolnay/quote) | `1.0.43` | `1.0.44` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.15` | `1.0.17` |



Updates `signal-hook` from 0.4.1 to 0.4.3
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.4.1...v0.4.3)

Updates `uuid` from 1.19.0 to 1.20.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.19.0...v1.20.0)

Updates `zerocopy` from 0.8.33 to 0.8.34
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.33...v0.8.34)

Updates `gdbstub` from 0.7.8 to 0.7.9
- [Release notes](https://github.com/daniel5151/gdbstub/releases)
- [Changelog](https://github.com/daniel5151/gdbstub/blob/master/CHANGELOG.md)
- [Commits](https://github.com/daniel5151/gdbstub/compare/0.7.8...0.7.9)

Updates `cc` from 1.2.53 to 1.2.54
- [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.53...cc-v1.2.54)

Updates `proc-macro2` from 1.0.105 to 1.0.106
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.105...1.0.106)

Updates `quote` from 1.0.43 to 1.0.44
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.43...1.0.44)

Updates `zerocopy-derive` from 0.8.33 to 0.8.34
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.33...v0.8.34)

Updates `zmij` from 1.0.15 to 1.0.17
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.15...1.0.17)

Updates `signal-hook` from 0.4.1 to 0.4.3
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.4.1...v0.4.3)

Updates `uuid` from 1.19.0 to 1.20.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.19.0...v1.20.0)

Updates `zerocopy` from 0.8.33 to 0.8.34
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.33...v0.8.34)

Updates `gdbstub` from 0.7.8 to 0.7.9
- [Release notes](https://github.com/daniel5151/gdbstub/releases)
- [Changelog](https://github.com/daniel5151/gdbstub/blob/master/CHANGELOG.md)
- [Commits](https://github.com/daniel5151/gdbstub/compare/0.7.8...0.7.9)

Updates `cc` from 1.2.53 to 1.2.54
- [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.53...cc-v1.2.54)

Updates `proc-macro2` from 1.0.105 to 1.0.106
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.105...1.0.106)

Updates `quote` from 1.0.43 to 1.0.44
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.43...1.0.44)

Updates `zerocopy-derive` from 0.8.33 to 0.8.34
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.33...v0.8.34)

Updates `zmij` from 1.0.15 to 1.0.17
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.15...1.0.17)

---
updated-dependencies:
- dependency-name: signal-hook
  dependency-version: 0.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: gdbstub
  dependency-version: 0.7.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.54
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: proc-macro2
  dependency-version: 1.0.106
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: quote
  dependency-version: 1.0.44
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.34
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.17
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: signal-hook
  dependency-version: 0.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: uuid
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: gdbstub
  dependency-version: 0.7.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.54
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: proc-macro2
  dependency-version: 1.0.106
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: quote
  dependency-version: 1.0.44
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.34
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.17
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 01:54:35 +00:00
dependabot[bot]
76822d853a build: Bump crate-ci/typos from 1.42.1 to 1.42.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.1 to 1.42.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.42.1...v1.42.2)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.42.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 01:45:45 +00:00
Anatol Belski
919cce9b58 tests: qcow: Add dirty bit integration tests
Add tests to verify dirty bit is set while VM runs and cleared on
clean shutdown. As part of it, ensure graceful shutdown when OS
disk verification requires consistent image state.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-26 11:24:35 +00:00
Anatol Belski
82dc9bfaee tests: qcow: Add unit tests for dirty bit support
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>
2026-01-26 11:24:35 +00:00
Anatol Belski
cc96fc14b4 block: qcow: Implement dirty bit support for QCOW2 v3 images
Add support for the dirty bit (bit 0 of incompatible_features) which
indicates the image was not closed cleanly. This improves data
integrity by allowing detection of potentially corrupted images.

On open:
- If dirty bit is already set, log a warning and trigger
  refcount rebuild
- Set the dirty bit and write it to disk immediately
- Sync to ensure persistence before any writes
- Skip dirty bit and refcount rebuild for readonly files

On clean close:
- Clear the dirty bit in the header
- Write it to disk and sync

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-26 11:24:35 +00:00
Anatol Belski
a6aecad635 tests: qcow: Add unit tests for variable refcount widths
Test all refcount_order values (0-6):
- Basic open for each width
- Write/read roundtrip
- Overwrite and multi-cluster allocation
- L2 cache eviction under memory pressure
- Sub-byte and byte-aligned max value handling
- Overflow error detection

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-25 09:36:42 +00:00
Anatol Belski
6e7f888f5d block: qcow: Add refcount overflow protection
Reject refcount values exceeding the maximum for the image's
refcount_order. This prevents silent truncation when storing
refcounts in narrow widths (e.g., 1-bit max is 1, 4-bit max is 15,
etc.).

Returns RefcountOverflow error with the attempted value, maximum,
and bit width. Propagates as EINVAL to the guest.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-25 09:36:42 +00:00
Anatol Belski
f8008191d2 block: qcow: Add support variable refcount widths
QCOW2 v3 specifies refcount_order 0-6 with
refcount_bits = 1 << refcount_order. Previously only 16-bit (order 4)
was supported.

Changes:
- RefcountBytes trait handles byte-aligned types (8/16/32/64-bit)
- Generic pack/unpack for sub-byte widths (1/2/4-bit)
- Function pointers for read/write selected at open time
- Internal refcount type widened from u16 to u64

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-25 09:36:42 +00:00
Anatol Belski
e61901dfdc block: qcow: Add tests for incompatible feature bit rejection
Add test cases verifying QCOW2 v3 images with unsupported incompatible
feature bits are correctly rejected.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-23 18:58:56 +00:00
Anatol Belski
7c99c169ba block: qcow: Validate incompatible feature bits
Parse the feature name table header extension to provide descriptive
error messages when unsupported incompatible features are detected.
Currently only the compression bit (bit 3, zstd) is supported.

This prevents opening qcow2 images with features that would cause
incorrect behavior or data corruption (e.g., dirty bit, corrupt bit,
external data file, extended L2 entries).

Feature names are defined as follows:
1. The image's feature name table header extension (if present)
2. Hardcoded fallback names for known features
3. Generic "unknown feature bit N" for undefined features

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
Co-developed-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2026-01-23 18:58:56 +00:00
Anatol Belski
eaafe426a6 tests: qcow: Add unit test for zero bit helpers
Add test for l2_entry_is_zero() and related helper functions.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-22 19:28:05 +00:00
Anatol Belski
13198777dd block: qcow: Add support for zero bit in standard L2 clusters
Implement read support for bit 0 in QCOW2 L2 table entries.
When this flag is set, the cluster reads as zeros without accessing
disk. This improves compatibility with QCOW2 images that use this
optimization.

According to the QCOW2 specification, bit 0 of the standard cluster
descriptor indicates that the cluster reads as zeros. Unlike
l2_entry == 0 indicating a completely unallocated entry, bit 0 can
be set on an allocated cluster.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-22 19:28:05 +00:00
Saravanan D
d58e421eea scripts: fix build_edk2 built marker creation
build_edk2 was leaving behind .built markers
even when compilation failed.

Gate creation of.built marker to occur only on
successful build

Modify build_edk2() to exit with error code
when arm64 firmware artifact : CLOUDHV_EFI.fd
is not produced

Fixes #7608

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-01-22 01:45:43 +00:00
Saravanan D
298da55902 scripts: fix build_edk2 compilation
build_edk2() module in scripts/common-aarch64.sh
does not produce the UEFI firmware for aarch64 as
the commits used to assemble sources for acpica,
edk2-platforms and edk2 do not compile after GCC
version upgraded from 11.4.0 to 13.3.0 in the
developer container (ubuntu 22.04 to 24.04)

Apply minimum upgrade to EDK2_REPO and ACPICA_REPO
required to compile with GCC 13.3.0
while still assuring guest VM boot for all
integration tests

BaseTools: Brotli compression submodule that was
previously failing has been fixed following commit
bump

Developers can now produce UEFI firmware for
aarch64 using the following commands
```
./scripts/dev_cli.sh shell
source scripts/test-util.sh
source scripts/common-aarch64.sh
build_edk2
```

Update docs/uefi.md

Fixes #7608

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
2026-01-22 01:45:43 +00:00
Muminul Islam
c9cd82b52b vmm: fix CVM boot failure on MSHV
Recent changes related to arm64 support in MSHV exposed
inconsistencies in the VM initialization and CVM boot paths.
The VM creation flow currently diverges across multiple scenarios,
including regular MSHV, CVM, and arm64, with each path performing
guest initialization steps in a different order.
Certain platform-specific requirements further constrain the ordering
of operations, such as the timing of address space creation,
IGVM loading, interrupt controller setup, and payload loading. For
CVM case address-space creation must be done after IGVM loading, and
PSP measurement. For Regular and arm64 this memory initialization
must be done early. For MSHV, vm.init() and sev_snp.init() are called in
different order which is run time and build time conditionally checked.

Additionally, while the KVM initialization path differs slightly
from MSHV, it shares common logic that is currently split across
separate conditional and build-time code paths, contributing to
fragmentation of the overall flow.

This change restructures the VM creation and initialization sequence
to better align shared logic, enforce scenario-specific ordering
constraints, and ensure consistent and correct behavior across all
supported configurations. In doing so, it restores proper CVM boot
behavior and improves the maintainability of the initialization code.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2026-01-21 19:40:17 +00:00
Rob Bradford
25e8e64a01 vhost_user_block: Return subset of config space requested
The spec says simply that that an empty payload should be returned on
error. Be slightly more helpful by adding a warning.

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-01-20 08:14:46 +00:00
Rob Bradford
15d34ffebb virtio-devices: vhost-user: blk: Use zero configuration offset
Based upon the discussion and in
https://github.com/rust-vmm/vhost/issues/29#issue-830820820 and the QEMU
behaviour the get_config offset should be zero. This was not caught by
our integration tests as the vhost-user-blk backend as implemented in
this repository does not use the offset.

Fixes: #7615

Signed-off-by: Rob Bradford <rbradford@meta.com>
2026-01-20 08:14:46 +00:00
dependabot[bot]
cda1ec5fbc build: Bump the non-rust-vmm group across 2 directories with 14 updates
Bumps the non-rust-vmm group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [igvm](https://github.com/microsoft/igvm) | ``81dd0e4`` | ``9848d1f`` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.17` | `2.0.18` |
| [zbus](https://github.com/z-galaxy/zbus) | `5.13.1` | `5.13.2` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.52` | `1.2.53` |
| [rand_core](https://github.com/rust-random/rand_core) | `0.9.4` | `0.9.5` |
| [rustc-demangle](https://github.com/rust-lang/rustc-demangle) | `0.1.26` | `0.1.27` |
| [wasip2](https://github.com/bytecodealliance/wasi-rs) | `1.0.1+wasi-0.2.4` | `1.0.2+wasi-0.2.9` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.13` | `1.0.15` |

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

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.17` | `2.0.18` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.52` | `1.2.53` |
| [rand_core](https://github.com/rust-random/rand_core) | `0.9.4` | `0.9.5` |
| [wasip2](https://github.com/bytecodealliance/wasi-rs) | `1.0.1+wasi-0.2.4` | `1.0.2+wasi-0.2.9` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.13` | `1.0.15` |



Updates `igvm` from `81dd0e4` to `9848d1f`
- [Release notes](https://github.com/microsoft/igvm/releases)
- [Commits](81dd0e49cd...9848d1f2a8)

Updates `igvm_defs` from `81dd0e4` to `9848d1f`
- [Release notes](https://github.com/microsoft/igvm/releases)
- [Commits](81dd0e49cd...9848d1f2a8)

Updates `thiserror` from 2.0.17 to 2.0.18
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.17...2.0.18)

Updates `zbus` from 5.13.1 to 5.13.2
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus-5.13.1...zbus-5.13.2)

Updates `cc` from 1.2.52 to 1.2.53
- [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.52...cc-v1.2.53)

Updates `find-msvc-tools` from 0.1.7 to 0.1.8
- [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/find-msvc-tools-v0.1.7...find-msvc-tools-v0.1.8)

Updates `rand_core` from 0.9.4 to 0.9.5
- [Changelog](https://github.com/rust-random/rand_core/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand_core/commits)

Updates `rustc-demangle` from 0.1.26 to 0.1.27
- [Release notes](https://github.com/rust-lang/rustc-demangle/releases)
- [Changelog](https://github.com/rust-lang/rustc-demangle/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/rustc-demangle/compare/rustc-demangle-v0.1.26...rustc-demangle-v0.1.27)

Updates `wasip2` from 1.0.1+wasi-0.2.4 to 1.0.2+wasi-0.2.9
- [Commits](https://github.com/bytecodealliance/wasi-rs/compare/wasip2-1.0.1...wasip2-1.0.2)

Updates `wit-bindgen` from 0.46.0 to 0.51.0
- [Release notes](https://github.com/bytecodealliance/wit-bindgen/releases)
- [Commits](https://github.com/bytecodealliance/wit-bindgen/compare/v0.46.0...v0.51.0)

Updates `zbus_macros` from 5.13.1 to 5.13.2
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus_macros-5.13.1...zbus_macros-5.13.2)

Updates `zmij` from 1.0.13 to 1.0.15
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.13...1.0.15)

Updates `zvariant` from 5.9.1 to 5.9.2
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant-5.9.1...zvariant-5.9.2)

Updates `zvariant_derive` from 5.9.1 to 5.9.2
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant_derive-5.9.1...zvariant_derive-5.9.2)

Updates `thiserror` from 2.0.17 to 2.0.18
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.17...2.0.18)

Updates `cc` from 1.2.52 to 1.2.53
- [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.52...cc-v1.2.53)

Updates `find-msvc-tools` from 0.1.7 to 0.1.8
- [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/find-msvc-tools-v0.1.7...find-msvc-tools-v0.1.8)

Updates `rand_core` from 0.9.4 to 0.9.5
- [Changelog](https://github.com/rust-random/rand_core/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand_core/commits)

Updates `wasip2` from 1.0.1+wasi-0.2.4 to 1.0.2+wasi-0.2.9
- [Commits](https://github.com/bytecodealliance/wasi-rs/compare/wasip2-1.0.1...wasip2-1.0.2)

Updates `wit-bindgen` from 0.46.0 to 0.51.0
- [Release notes](https://github.com/bytecodealliance/wit-bindgen/releases)
- [Commits](https://github.com/bytecodealliance/wit-bindgen/compare/v0.46.0...v0.51.0)

Updates `zmij` from 1.0.13 to 1.0.15
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.13...1.0.15)

---
updated-dependencies:
- dependency-name: igvm
  dependency-version: 9848d1f2a8a44931395a30debeae287b6d01505d
  dependency-type: direct:production
  dependency-group: non-rust-vmm
- dependency-name: igvm_defs
  dependency-version: 9848d1f2a8a44931395a30debeae287b6d01505d
  dependency-type: direct:production
  dependency-group: non-rust-vmm
- dependency-name: thiserror
  dependency-version: 2.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zbus
  dependency-version: 5.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.53
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: find-msvc-tools
  dependency-version: 0.1.8
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand_core
  dependency-version: 0.9.5
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rustc-demangle
  dependency-version: 0.1.27
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasip2
  dependency-version: 1.0.2+wasi-0.2.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wit-bindgen
  dependency-version: 0.51.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zbus_macros
  dependency-version: 5.13.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.15
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zvariant
  dependency-version: 5.9.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zvariant_derive
  dependency-version: 5.9.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: thiserror
  dependency-version: 2.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.53
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: find-msvc-tools
  dependency-version: 0.1.8
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand_core
  dependency-version: 0.9.5
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wasip2
  dependency-version: 1.0.2+wasi-0.2.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: wit-bindgen
  dependency-version: 0.51.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.15
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 01:29:32 +00:00
dependabot[bot]
cdf14956e0 build: Bump crate-ci/typos from 1.42.0 to 1.42.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.0 to 1.42.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.42.0...v1.42.1)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.42.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 01:18:07 +00:00
Anirudh Rayabharam
e4f9610b5b build: bump mshv crates to 0.6.6
Consume the latest mshv crates.

Refer to https://github.com/rust-vmm/mshv/pull/294 for the changelog.

Signed-off-by: Anirudh Rayabharam <anrayabh@microsoft.com>
2026-01-19 16:22:51 +00:00
Aastha Rawat
d5e35d9e5c ci: get rid of continue-on-error from mshv workflows
Since the mshv integration workflow has been stable for a long time,
make the workflows no longer optional.

Signed-off-by: Aastha Rawat <aastharawat@microsoft.com>
2026-01-19 15:33:53 +00:00
Bo Chen
ccf0b9ec7d vmm: Refactor IORT table generation to use structured data types
The current implementation is based on IORT spec revisition E.b [1].

[1] https://developer.arm.com/documentation/den0049/eb/?lang=en

Fixes: #7587

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-01-16 19:07:44 +00:00
Anatol Belski
cde7856d9e performance-metrics: Add warmup support for warm cache testing
Add warmup_iterations field to run iterations before measuring
performance. This complements existing cold start tests
by separating cache effects from steady state throughput.

New tests with 2 warmup iterations:
- block_qcow2_backing_qcow2_read_warm_MiBps
- block_qcow2_backing_raw_read_warm_MiBps

Results show warm cache is much faster and more consistent:
- QCOW2: 1766 MiB/s (4% variance) vs cold 960 MiB/s (73% variance)
- RAW: 1822 MiB/s (6% variance) vs cold 1300 MiB/s (55% variance)

RAW backing is 3% faster than QCOW2 in steady state.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-16 18:54:22 +00:00
Alyssa Ross
dee7993538 docs: fix gdb path option name
Fixes: fa22cb0be ("docs: update command line options to use clap")
Signed-off-by: Alyssa Ross <hi@alyssa.is>
2026-01-15 18:55:36 +00:00
Anatol Belski
4f69cf604a tests: qcow: Add backing file verification for qcow2 images
- Automatically detect and verify backing files
- Verify backing file integrity with qemu-img check (qcow only)
- Compute DJB2 checksums before test to detect modifications

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-15 16:19:15 +00:00
Anatol Belski
3fed706d6a block: qcow: Fix v3 header writing and add extension tests
The write_to() function is used by test code to create qcow2 files for
testing. For v3 headers with extended header_size (>104), it needs to:

1. Write the mandatory compression_type field at bytes 104-111
2. Write the header extension end marker at the header_size offset
3. Seek to backing_file_offset before writing the backing file path

Additionally, create_for_size_and_path() must set backing_file_offset
to account for the 8 byte extension end marker in v3 files, so the
backing file path doesn't overwrite the extension area.

Add unit tests for read_header_extensions() covering backing format
parsing (raw/qcow2), unknown extensions, and error cases (invalid
formats, invalid UTF-8). These tests depend on the header writing fixes
to create properly formatted v3 test files.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-15 16:19:15 +00:00
Anatol Belski
9eb2b9b0e5 block: qcow: Implement extension parsing for QCOW v3
Add support for parsing QCOW v3 header extensions to read the
backing file format. The QCOW v3 spec allows optional header
extensions between the fixed header and the backing file name.

Implement read_header_extensions() to parse the extension area,
which starts at the header_size offset. At the moment it is
used to read the backing file format. Further extension
processing is open in folow up implementations.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-15 16:19:15 +00:00
Anatol Belski
10394da0c4 performance-metrics: Add RAW backing file performance tests
Add sequential and random read tests for QCOW2 overlays with
RAW backing files.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-15 16:19:15 +00:00
Anatol Belski
b3922dbd2c block: qcow: Add raw backing file support
Add support for raw backing files in addition to qcow2 backing
files. This enables QCOW2 overlays to use raw images as their
backing store.

The backing file format is auto-detected when not specified,
using the existing detect_image_type() function.

Add backing_file_format field to QcowHeader to store the format
type, which will be populated from header extensions by a
subsequent patch.

Modify new_from_backing() to accept a backing_format parameter,
consolidating support for both raw and qcow2 backing files in a
single function. The backing_file_size parameter allows overlay
creation without opening the backing file multiple times.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-15 16:19:15 +00:00
Anatol Belski
cb495959a8 tests: qcow: Add testing for backing file in raw format
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-15 16:19:15 +00:00
Stanislav Kinsburskii
de2d8f486b hypervisor: mshv: Introduce CPU state update function and use it
In most of the cases, special registers don't change after emulations,
but current code sets them back unconditionally, and although some of
them are set over the register page, others require a system call and a
hypervisor to be updated, which is a waste it there were not changes.

Introduce and CPU update method for Microsoft Hypervisor emulator and
set special registers only when they were changed. This change reduces
guest boot time by 4% for a single VP guest boot (in L1VH partition) in
my experiments.

Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
2026-01-14 10:57:20 +00:00
Stanislav Kinsburskii
5aba9b4308 hypervisor: emulator: Pass CPU context to instruction stream emulator
This is a precursor change to overall ioctl and hypercall reduction
effort. The old (current) CPU state can be compared to the new to
determine what has changed and avoid unnecessary register updates.

Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
2026-01-14 10:57:20 +00:00
Yi Wang
03252f5851 docs: fix some syntax and bash usage
Minor modifications were made to make the sentences sound more natural.
Also fixed some parameter usage issues in bash code block.

Signed-off-by: Yi Wang <foxywang@tencent.com>
2026-01-14 09:20:34 +00:00
Yi Wang
34b8aed662 docs: fix some syntax and format issues in tdx/sev
There are some syntax and format issues in tdx/sev documents.
Make some modification to make the description more natural.

And the link of SEV-SNP is invalid, fix it.

Signed-off-by: Yi Wang <foxywang@tencent.com>
2026-01-14 09:20:34 +00:00
Yi Wang
2c2dec50f9 docs: fix minor syntax issues for debug
There are some minor syntax and command issues in debug-port document.

As commit 5febdec81a (vmm: Enable `gdbstub` on AArch64) supported
aarch64, the docs should keep consistent.

Signed-off-by: Yi Wang <foxywang@tencent.com>
2026-01-14 09:20:34 +00:00
Yi Wang
e7a151e953 docs: fix some minor syntax issues in api/building
Fix some minor syntax issues in api/building document to make
the sentences more fluent and easy to read.

Signed-off-by: Yi Wang <foxywang@tencent.com>
2026-01-14 09:20:34 +00:00
Yi Wang
7b92a36c4c docs: fix syntax issues and struct description in devices
Some description in the device document were inconsistent with the
source code. Also fix some syntax issues to make the sentences more
fluent.

Signed-off-by: Yi Wang <foxywang@tencent.com>
2026-01-14 09:20:34 +00:00
Yi Wang
34ee973ee0 scripts: fix warning in test util
CI reports:
In scripts/test-util.sh line 216:
    cleanup() {
    ^-- SC2329 (info): This function is never invoked. Check usage (or ignored if invoked indirectly).

The shellcheck can't trace calling in trap, so we need add hint
to make it happy.

Signed-off-by: Yi Wang <foxywang@tencent.com>
2026-01-14 09:04:07 +00:00
dependabot[bot]
c19ee037a2 build: Bump the non-rust-vmm group across 2 directories with 14 updates
Bumps the non-rust-vmm group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [libc](https://github.com/rust-lang/libc) | `0.2.179` | `0.2.180` |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.1.5` | `1.1.8` |
| [zbus](https://github.com/z-galaxy/zbus) | `5.12.0` | `5.13.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.51` | `1.2.52` |
| [clap_lex](https://github.com/clap-rs/clap) | `0.7.6` | `0.7.7` |
| [rand_core](https://github.com/rust-random/rand) | `0.9.3` | `0.9.4` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.12` | `1.0.13` |

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

| Package | From | To |
| --- | --- | --- |
| [libc](https://github.com/rust-lang/libc) | `0.2.179` | `0.2.180` |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.1.5` | `1.1.8` |
| [getrandom](https://github.com/rust-random/getrandom) | `0.2.16` | `0.2.17` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.51` | `1.2.52` |
| [clap_lex](https://github.com/clap-rs/clap) | `0.7.6` | `0.7.7` |
| [rand_core](https://github.com/rust-random/rand) | `0.9.3` | `0.9.4` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.12` | `1.0.13` |



Updates `libc` from 0.2.179 to 0.2.180
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.180/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.179...0.2.180)

Updates `flate2` from 1.1.5 to 1.1.8
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.1.5...1.1.8)

Updates `zbus` from 5.12.0 to 5.13.1
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus-5.12.0...zbus-5.13.1)

Updates `cc` from 1.2.51 to 1.2.52
- [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.51...cc-v1.2.52)

Updates `clap_lex` from 0.7.6 to 0.7.7
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_lex-v0.7.6...clap_lex-v0.7.7)

Updates `find-msvc-tools` from 0.1.6 to 0.1.7
- [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/find-msvc-tools-v0.1.6...find-msvc-tools-v0.1.7)

Updates `rand_core` from 0.9.3 to 0.9.4
- [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/commits)

Updates `zbus_macros` from 5.12.0 to 5.13.1
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus-5.12.0...zbus_macros-5.13.1)

Updates `zbus_names` from 4.2.0 to 4.3.1
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus_names-4.2.0...zbus_names-4.3.1)

Updates `zmij` from 1.0.12 to 1.0.13
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.12...1.0.13)

Updates `zvariant` from 5.8.0 to 5.9.1
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant-5.8.0...zvariant-5.9.1)

Updates `zvariant_derive` from 5.8.0 to 5.9.1
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zbus-5.8.0...zvariant_derive-5.9.1)

Updates `zvariant_utils` from 3.2.1 to 3.3.0
- [Release notes](https://github.com/z-galaxy/zbus/releases)
- [Changelog](https://github.com/z-galaxy/zbus/blob/main/release-plz.toml)
- [Commits](https://github.com/z-galaxy/zbus/compare/zvariant-3.2.1...zvariant_utils-3.3.0)

Updates `libc` from 0.2.179 to 0.2.180
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.180/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.179...0.2.180)

Updates `flate2` from 1.1.5 to 1.1.8
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.1.5...1.1.8)

Updates `getrandom` from 0.2.16 to 0.2.17
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.2.16...v0.2.17)

Updates `cc` from 1.2.51 to 1.2.52
- [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.51...cc-v1.2.52)

Updates `clap_lex` from 0.7.6 to 0.7.7
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_lex-v0.7.6...clap_lex-v0.7.7)

Updates `find-msvc-tools` from 0.1.6 to 0.1.7
- [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/find-msvc-tools-v0.1.6...find-msvc-tools-v0.1.7)

Updates `rand_core` from 0.9.3 to 0.9.4
- [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/commits)

Updates `zmij` from 1.0.12 to 1.0.13
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.12...1.0.13)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.180
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: flate2
  dependency-version: 1.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zbus
  dependency-version: 5.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.52
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_lex
  dependency-version: 0.7.7
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: find-msvc-tools
  dependency-version: 0.1.7
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand_core
  dependency-version: 0.9.4
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zbus_macros
  dependency-version: 5.13.1
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zbus_names
  dependency-version: 4.3.1
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.13
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zvariant
  dependency-version: 5.9.1
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zvariant_derive
  dependency-version: 5.9.1
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: zvariant_utils
  dependency-version: 3.3.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.180
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: flate2
  dependency-version: 1.1.8
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: getrandom
  dependency-version: 0.2.17
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.52
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_lex
  dependency-version: 0.7.7
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: find-msvc-tools
  dependency-version: 0.1.7
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rand_core
  dependency-version: 0.9.4
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.13
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 05:09:15 +00:00
Bo Chen
11edb28069 vmm: acpi: Clarify device ID partitioning in IORT table
The IORT table's ID mapping uses a 256-ID offset per PCI segment to
ensure unique device IDs across all segments. This partitioning scheme
(output_base = 256 * segment_id) must match the device ID encoding used
in KVM MSI routing configuration [1].

This mapping assumes one bus per PCI segment, and supports up to 256 PCI
segments in the system.

[1] c9374d87ac

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-01-12 17:18:52 +00:00
Bo Chen
01d711e893 vmm: acpi: Explicitly write ITS identifier in IORT table
The IORT specification (Revision E.b, Table 12) defines the ITS Group
Node structure with an ITS Identifiers array following the node header.
Although the IORT table is zero-initialized, this commit adds an
explicit write of the ITS identifier value (0) for clarity and spec
compliance.

This ITS identifier must match the `translation_id` field in the MADT
GIC ITS structure to ensure proper interrupt routing on ARM platforms.

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-01-12 17:18:52 +00:00
Bo Chen
c87ca39219 vmm: acpi: Generate IORT table according to spec revision E.b
The current IORT table implementation is based on IORT Spec revision E.b
[1], as evidenced by:
* The PCI root complex node revision being set to `3`
* The code being updated in late 2021 [2] when revision E.b was the
  latest version

This patch ensures the IORT table is properly generated according to
this specification revision, fixing three issues:

1. The IORT table revision should be `3` rather than `2` (see Table 2 in
   the spec [1])

2. The GIC ITS group node revision should be `1` rather than `0`
   (see Table 12 in the spec [1])

3. The "Memory access properties" and "ATS Attribute" fields of the PCI
   root complex node  was set incorrectly - specifically the MAF (Memory
   Access Flags) including CPM and DACS bits (see Tables 14, 15, and 17
   in the spec [1])

[1] https://developer.arm.com/documentation/den0049/eb/?lang=en
[2] https://github.com/cloud-hypervisor/cloud-hypervisor/pull/3356

Signed-off-by: Bo Chen <bchen@crusoe.ai>
2026-01-12 17:18:52 +00:00
Demi Marie Obenour
079d94ecae seccomp: Add gettid() to all seccomp filters
It should always succeed and is apparently implicitly called by libc or
some dependency somewhere.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-01-10 19:18:04 +00:00
dependabot[bot]
847de7e86e build: Bump crate-ci/typos from 1.41.0 to 1.42.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.41.0 to 1.42.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.41.0...v1.42.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-08 02:30:09 +00:00
dependabot[bot]
c396f1ad2a build: Bump the non-rust-vmm group across 2 directories with 9 updates
Bumps the non-rust-vmm group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.148` | `1.0.149` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.31` | `0.8.33` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.12.1` | `2.13.0` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.17` | `0.2.18` |
| [quote](https://github.com/dtolnay/quote) | `1.0.42` | `1.0.43` |
| [syn](https://github.com/dtolnay/syn) | `2.0.113` | `2.0.114` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.11` | `1.0.12` |

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

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.148` | `1.0.149` |
| [zerocopy](https://github.com/google/zerocopy) | `0.8.31` | `0.8.33` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.12.1` | `2.13.0` |
| [quote](https://github.com/dtolnay/quote) | `1.0.42` | `1.0.43` |
| [syn](https://github.com/dtolnay/syn) | `2.0.113` | `2.0.114` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.11` | `1.0.12` |



Updates `serde_json` from 1.0.148 to 1.0.149
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.148...v1.0.149)

Updates `zerocopy` from 0.8.31 to 0.8.33
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.31...v0.8.33)

Updates `indexmap` from 2.12.1 to 2.13.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.12.1...2.13.0)

Updates `jiff` from 0.2.17 to 0.2.18
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.17...jiff-static-0.2.18)

Updates `jiff-static` from 0.2.17 to 0.2.18
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.17...jiff-static-0.2.18)

Updates `quote` from 1.0.42 to 1.0.43
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.42...1.0.43)

Updates `syn` from 2.0.113 to 2.0.114
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.113...2.0.114)

Updates `zerocopy-derive` from 0.8.31 to 0.8.33
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.31...v0.8.33)

Updates `zmij` from 1.0.11 to 1.0.12
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.11...1.0.12)

Updates `serde_json` from 1.0.148 to 1.0.149
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.148...v1.0.149)

Updates `zerocopy` from 0.8.31 to 0.8.33
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.31...v0.8.33)

Updates `indexmap` from 2.12.1 to 2.13.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.12.1...2.13.0)

Updates `quote` from 1.0.42 to 1.0.43
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.42...1.0.43)

Updates `syn` from 2.0.113 to 2.0.114
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.113...2.0.114)

Updates `zerocopy-derive` from 0.8.31 to 0.8.33
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.8.31...v0.8.33)

Updates `zmij` from 1.0.11 to 1.0.12
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.11...1.0.12)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.149
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: indexmap
  dependency-version: 2.13.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.18
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.18
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: quote
  dependency-version: 1.0.43
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.114
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.33
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_json
  dependency-version: 1.0.149
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy
  dependency-version: 0.8.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: indexmap
  dependency-version: 2.13.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: quote
  dependency-version: 1.0.43
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.114
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zerocopy-derive
  dependency-version: 0.8.33
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-07 18:10:23 +00:00
Demi Marie Obenour
2e05836669 hypervisor: Suppress unused_unsafe warning
x86::__cpuid is safe on Rust ≥1.94 but unsafe on older versions.  This
causes unused_unsafe warnings when compiling with Rust ≥1.94.  However,
on earlier Rust versions, the code won’t compile if the unsafe blocks
are absent.

Work around this by adding #[allow(unused_unsafe)] where needed to
suppress the warnings.

See #7588 for more discussion.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2026-01-07 00:20:41 +00:00
dependabot[bot]
f6f0d49c62 build: Bump the non-rust-vmm group across 2 directories with 6 updates
Bumps the non-rust-vmm group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.5.53` | `4.5.54` |
| [libc](https://github.com/rust-lang/libc) | `0.2.178` | `0.2.179` |
| [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.104` | `1.0.105` |
| [syn](https://github.com/dtolnay/syn) | `2.0.111` | `2.0.113` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.3` | `1.0.11` |

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

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.5.53` | `4.5.54` |
| [libc](https://github.com/rust-lang/libc) | `0.2.178` | `0.2.179` |
| [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.104` | `1.0.105` |
| [syn](https://github.com/dtolnay/syn) | `2.0.111` | `2.0.113` |
| [zmij](https://github.com/dtolnay/zmij) | `1.0.3` | `1.0.11` |



Updates `clap` from 4.5.53 to 4.5.54
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.53...clap_complete-v4.5.54)

Updates `libc` from 0.2.178 to 0.2.179
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.178...0.2.179)

Updates `clap_builder` from 4.5.53 to 4.5.54
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.53...v4.5.54)

Updates `proc-macro2` from 1.0.104 to 1.0.105
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.104...1.0.105)

Updates `syn` from 2.0.111 to 2.0.113
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.111...2.0.113)

Updates `zmij` from 1.0.3 to 1.0.11
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.3...1.0.11)

Updates `clap` from 4.5.53 to 4.5.54
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.53...clap_complete-v4.5.54)

Updates `libc` from 0.2.178 to 0.2.179
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.178...0.2.179)

Updates `clap_builder` from 4.5.53 to 4.5.54
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.53...v4.5.54)

Updates `proc-macro2` from 1.0.104 to 1.0.105
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.104...1.0.105)

Updates `syn` from 2.0.111 to 2.0.113
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.111...2.0.113)

Updates `zmij` from 1.0.3 to 1.0.11
- [Release notes](https://github.com/dtolnay/zmij/releases)
- [Commits](https://github.com/dtolnay/zmij/compare/1.0.3...1.0.11)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.54
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.179
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.54
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: proc-macro2
  dependency-version: 1.0.105
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.113
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.11
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap
  dependency-version: 4.5.54
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libc
  dependency-version: 0.2.179
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: clap_builder
  dependency-version: 4.5.54
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: proc-macro2
  dependency-version: 1.0.105
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: syn
  dependency-version: 2.0.113
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: zmij
  dependency-version: 1.0.11
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-06 00:33:49 +00:00
Anatol Belski
3657db7843 vmm: mshv: Set PROCESSORS_PER_SOCKET property for CPU topologies
On MSHV, exposing multithreaded CPU topologies requires setting the
PROCESSORS_PER_SOCKET partition property so that CPUID.0xB reports
correct logical processor counts and topology levels to the guest.

This property must be set after all vCPUs are configured, as the
hypervisor uses the complete vCPU layout to derive and report CPU
topology information.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-05 21:41:28 +00:00
Anatol Belski
dccdb223b7 tests: Re-enable CPU topology tests on MSHV
Fixes: #7433

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-01-05 21:41:28 +00:00
dependabot[bot]
de618ec35e build: Bump crate-ci/typos from 1.40.1 to 1.41.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.1 to 1.41.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.40.1...v1.41.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-01 00:04:28 +00:00
dependabot[bot]
379e2d4647 build: Bump the non-rust-vmm group across 2 directories with 14 updates
Bumps the non-rust-vmm group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.146` | `1.0.148` |
| [signal-hook](https://github.com/vorner/signal-hook) | `0.3.18` | `0.4.1` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.7.1` | `1.8.0` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.50` | `1.2.51` |
| [itoa](https://github.com/dtolnay/itoa) | `1.0.16` | `1.0.17` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.16` | `0.2.17` |
| libredox | `0.1.11` | `0.1.12` |
| [portable-atomic](https://github.com/taiki-e/portable-atomic) | `1.12.0` | `1.13.0` |
| [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.103` | `1.0.104` |
| [rustix](https://github.com/bytecodealliance/rustix) | `1.1.2` | `1.1.3` |
| [signal-hook-registry](https://github.com/vorner/signal-hook) | `1.4.7` | `1.4.8` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.23.0` | `3.24.0` |

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

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.146` | `1.0.148` |
| [signal-hook](https://github.com/vorner/signal-hook) | `0.3.18` | `0.4.1` |
| [arc-swap](https://github.com/vorner/arc-swap) | `1.7.1` | `1.8.0` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.50` | `1.2.51` |
| [itoa](https://github.com/dtolnay/itoa) | `1.0.16` | `1.0.17` |
| [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.103` | `1.0.104` |
| [signal-hook-registry](https://github.com/vorner/signal-hook) | `1.4.7` | `1.4.8` |



Updates `serde_json` from 1.0.146 to 1.0.148
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.146...v1.0.148)

Updates `signal-hook` from 0.3.18 to 0.4.1
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.3.18...v0.4.1)

Updates `arc-swap` from 1.7.1 to 1.8.0
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.7.1...v1.8.0)

Updates `cc` from 1.2.50 to 1.2.51
- [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.50...cc-v1.2.51)

Updates `find-msvc-tools` from 0.1.5 to 0.1.6
- [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/find-msvc-tools-v0.1.5...find-msvc-tools-v0.1.6)

Updates `itoa` from 1.0.16 to 1.0.17
- [Release notes](https://github.com/dtolnay/itoa/releases)
- [Commits](https://github.com/dtolnay/itoa/compare/1.0.16...1.0.17)

Updates `jiff` from 0.2.16 to 0.2.17
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.16...jiff-static-0.2.17)

Updates `jiff-static` from 0.2.16 to 0.2.17
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/jiff/compare/jiff-static-0.2.16...jiff-static-0.2.17)

Updates `libredox` from 0.1.11 to 0.1.12

Updates `portable-atomic` from 1.12.0 to 1.13.0
- [Release notes](https://github.com/taiki-e/portable-atomic/releases)
- [Changelog](https://github.com/taiki-e/portable-atomic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/portable-atomic/compare/v1.12.0...v1.13.0)

Updates `proc-macro2` from 1.0.103 to 1.0.104
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.103...1.0.104)

Updates `rustix` from 1.1.2 to 1.1.3
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v1.1.2...v1.1.3)

Updates `signal-hook-registry` from 1.4.7 to 1.4.8
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/registry-v1.4.7...registry-v1.4.8)

Updates `tempfile` from 3.23.0 to 3.24.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.23.0...v3.24.0)

Updates `serde_json` from 1.0.146 to 1.0.148
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.146...v1.0.148)

Updates `signal-hook` from 0.3.18 to 0.4.1
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.3.18...v0.4.1)

Updates `arc-swap` from 1.7.1 to 1.8.0
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.7.1...v1.8.0)

Updates `cc` from 1.2.50 to 1.2.51
- [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.50...cc-v1.2.51)

Updates `find-msvc-tools` from 0.1.5 to 0.1.6
- [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/find-msvc-tools-v0.1.5...find-msvc-tools-v0.1.6)

Updates `itoa` from 1.0.16 to 1.0.17
- [Release notes](https://github.com/dtolnay/itoa/releases)
- [Commits](https://github.com/dtolnay/itoa/compare/1.0.16...1.0.17)

Updates `proc-macro2` from 1.0.103 to 1.0.104
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.103...1.0.104)

Updates `signal-hook-registry` from 1.4.7 to 1.4.8
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/registry-v1.4.7...registry-v1.4.8)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.148
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: signal-hook
  dependency-version: 0.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.51
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: find-msvc-tools
  dependency-version: 0.1.6
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: itoa
  dependency-version: 1.0.17
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff
  dependency-version: 0.2.17
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: jiff-static
  dependency-version: 0.2.17
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libredox
  dependency-version: 0.1.12
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: portable-atomic
  dependency-version: 1.13.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: proc-macro2
  dependency-version: 1.0.104
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: rustix
  dependency-version: 1.1.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: signal-hook-registry
  dependency-version: 1.4.8
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: tempfile
  dependency-version: 3.24.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: serde_json
  dependency-version: 1.0.148
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: signal-hook
  dependency-version: 0.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: arc-swap
  dependency-version: 1.8.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.51
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: find-msvc-tools
  dependency-version: 0.1.6
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: itoa
  dependency-version: 1.0.17
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: proc-macro2
  dependency-version: 1.0.104
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: signal-hook-registry
  dependency-version: 1.4.8
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 04:25:34 +00:00
dependabot[bot]
14911a9518 build: Bump ryu from 1.0.21 to 1.0.22 in /fuzz
Bumps [ryu](https://github.com/dtolnay/ryu) from 1.0.21 to 1.0.22.
- [Release notes](https://github.com/dtolnay/ryu/releases)
- [Commits](https://github.com/dtolnay/ryu/compare/1.0.21...1.0.22)

---
updated-dependencies:
- dependency-name: ryu
  dependency-version: 1.0.22
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 01:23:44 +00:00
dependabot[bot]
f11682feea build: Bump ryu from 1.0.21 to 1.0.22
Bumps [ryu](https://github.com/dtolnay/ryu) from 1.0.21 to 1.0.22.
- [Release notes](https://github.com/dtolnay/ryu/releases)
- [Commits](https://github.com/dtolnay/ryu/compare/1.0.21...1.0.22)

---
updated-dependencies:
- dependency-name: ryu
  dependency-version: 1.0.22
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 00:24:38 +00:00
dependabot[bot]
db205150f9 build: Bump crate-ci/typos from 1.40.0 to 1.40.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.40.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.40.0...v1.40.1)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.40.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 00:05:16 +00:00
Demi Marie Obenour
aca7b01c6b net_util: Fix MAC address parsing
It wrongly allowed addresses containing a + instead of a hex character.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2025-12-29 12:09:53 +00:00
dependabot[bot]
d741cd53de build: Bump the non-rust-vmm group across 2 directories with 15 updates
Bumps the non-rust-vmm group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [igvm](https://github.com/microsoft/igvm) | ``5d2b5a5`` | ``81dd0e4`` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.145` | `1.0.146` |
| [async-lock](https://github.com/smol-rs/async-lock) | `3.4.1` | `3.4.2` |
| [bumpalo](https://github.com/fitzgen/bumpalo) | `3.19.0` | `3.19.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.49` | `1.2.50` |
| [itoa](https://github.com/dtolnay/itoa) | `1.0.15` | `1.0.16` |
| libredox | `0.1.10` | `0.1.11` |
| [portable-atomic](https://github.com/taiki-e/portable-atomic) | `1.11.1` | `1.12.0` |
| [ryu](https://github.com/dtolnay/ryu) | `1.0.20` | `1.0.21` |
| [toml_datetime](https://github.com/toml-rs/toml) | `0.7.3` | `0.7.5+spec-1.1.0` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.23.9` | `0.23.10+spec-1.0.0` |
| [tracing](https://github.com/tokio-rs/tracing) | `0.1.43` | `0.1.44` |

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

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.145` | `1.0.146` |
| [bumpalo](https://github.com/fitzgen/bumpalo) | `3.19.0` | `3.19.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.49` | `1.2.50` |
| [itoa](https://github.com/dtolnay/itoa) | `1.0.15` | `1.0.16` |
| [ryu](https://github.com/dtolnay/ryu) | `1.0.20` | `1.0.21` |
| [toml_datetime](https://github.com/toml-rs/toml) | `0.7.3` | `0.7.5+spec-1.1.0` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.23.9` | `0.23.10+spec-1.0.0` |



Updates `igvm` from `5d2b5a5` to `81dd0e4`
- [Release notes](https://github.com/microsoft/igvm/releases)
- [Commits](5d2b5a58e0...81dd0e49cd)

Updates `igvm_defs` from `5d2b5a5` to `81dd0e4`
- [Release notes](https://github.com/microsoft/igvm/releases)
- [Commits](5d2b5a58e0...81dd0e49cd)

Updates `serde_json` from 1.0.145 to 1.0.146
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.145...v1.0.146)

Updates `async-lock` from 3.4.1 to 3.4.2
- [Release notes](https://github.com/smol-rs/async-lock/releases)
- [Changelog](https://github.com/smol-rs/async-lock/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/async-lock/compare/v3.4.1...v3.4.2)

Updates `bumpalo` from 3.19.0 to 3.19.1
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.19.0...v3.19.1)

Updates `cc` from 1.2.49 to 1.2.50
- [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.49...cc-v1.2.50)

Updates `itoa` from 1.0.15 to 1.0.16
- [Release notes](https://github.com/dtolnay/itoa/releases)
- [Commits](https://github.com/dtolnay/itoa/compare/1.0.15...1.0.16)

Updates `libredox` from 0.1.10 to 0.1.11

Updates `portable-atomic` from 1.11.1 to 1.12.0
- [Release notes](https://github.com/taiki-e/portable-atomic/releases)
- [Changelog](https://github.com/taiki-e/portable-atomic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/portable-atomic/compare/v1.11.1...v1.12.0)

Updates `ryu` from 1.0.20 to 1.0.21
- [Release notes](https://github.com/dtolnay/ryu/releases)
- [Commits](https://github.com/dtolnay/ryu/compare/1.0.20...1.0.21)

Updates `toml_datetime` from 0.7.3 to 0.7.5+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.7.3...toml_datetime-v0.7.5)

Updates `toml_edit` from 0.23.9 to 0.23.10+spec-1.0.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.23.9...v0.23.10)

Updates `toml_parser` from 1.0.4 to 1.0.6+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.0.4...toml_parser-v1.0.6)

Updates `tracing` from 0.1.43 to 0.1.44
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.43...tracing-0.1.44)

Updates `tracing-core` from 0.1.35 to 0.1.36
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-core-0.1.35...tracing-core-0.1.36)

Updates `serde_json` from 1.0.145 to 1.0.146
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.145...v1.0.146)

Updates `bumpalo` from 3.19.0 to 3.19.1
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.19.0...v3.19.1)

Updates `cc` from 1.2.49 to 1.2.50
- [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.49...cc-v1.2.50)

Updates `itoa` from 1.0.15 to 1.0.16
- [Release notes](https://github.com/dtolnay/itoa/releases)
- [Commits](https://github.com/dtolnay/itoa/compare/1.0.15...1.0.16)

Updates `ryu` from 1.0.20 to 1.0.21
- [Release notes](https://github.com/dtolnay/ryu/releases)
- [Commits](https://github.com/dtolnay/ryu/compare/1.0.20...1.0.21)

Updates `toml_datetime` from 0.7.3 to 0.7.5+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.7.3...toml_datetime-v0.7.5)

Updates `toml_edit` from 0.23.9 to 0.23.10+spec-1.0.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.23.9...v0.23.10)

Updates `toml_parser` from 1.0.4 to 1.0.6+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/toml_parser-v1.0.4...toml_parser-v1.0.6)

---
updated-dependencies:
- dependency-name: igvm
  dependency-version: 81dd0e49cd3caf9e894e376969e33f2584e6f8ba
  dependency-type: direct:production
  dependency-group: non-rust-vmm
- dependency-name: igvm_defs
  dependency-version: 81dd0e49cd3caf9e894e376969e33f2584e6f8ba
  dependency-type: direct:production
  dependency-group: non-rust-vmm
- dependency-name: serde_json
  dependency-version: 1.0.146
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: async-lock
  dependency-version: 3.4.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bumpalo
  dependency-version: 3.19.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.50
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: itoa
  dependency-version: 1.0.16
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: libredox
  dependency-version: 0.1.11
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: portable-atomic
  dependency-version: 1.12.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: non-rust-vmm
- dependency-name: ryu
  dependency-version: 1.0.21
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_datetime
  dependency-version: 0.7.5+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.23.10+spec-1.0.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_parser
  dependency-version: 1.0.6+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: tracing
  dependency-version: 0.1.44
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: tracing-core
  dependency-version: 0.1.36
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: serde_json
  dependency-version: 1.0.146
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: bumpalo
  dependency-version: 3.19.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: cc
  dependency-version: 1.2.50
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: itoa
  dependency-version: 1.0.16
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: ryu
  dependency-version: 1.0.21
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_datetime
  dependency-version: 0.7.5+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_edit
  dependency-version: 0.23.10+spec-1.0.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
- dependency-name: toml_parser
  dependency-version: 1.0.6+spec-1.1.0
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: non-rust-vmm
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-23 09:16:45 +00:00
Demi Marie Obenour
2278a5236a misc: Fix mis-sorting of command line options
This un-breaks the test suite with the dbus_api feature enabled.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
2025-12-20 07:11:11 +00:00
397 changed files with 74897 additions and 27817 deletions

View File

@@ -1,3 +1,38 @@
[profile.default]
# Don't let one individual test run for more than 10 minutes
slow-timeout = { period = "60s", terminate-after = 10 }
[profile.integration]
fail-fast = false
retries = 3
[profile.common_tests]
inherits = "integration"
default-filter = 'test(common_parallel::) | test(common_sequential::) | test(aarch64_acpi::)'
junit.path = "/root/workloads/junit/common.xml"
[[profile.common_tests.overrides]]
filter = 'test(common_sequential::)'
# use up all the available test threads for each of the sequential tests
# i.e. no other test can be running while a sequential test is running.
threads-required = 'num-test-threads'
[profile.dbus]
inherits = "integration"
default-filter = 'test(dbus_api::)'
junit.path = "/root/workloads/junit/dbus.xml"
[profile.fw_cfg]
inherits = "integration"
default-filter = 'test(fw_cfg::)'
junit.path = "/root/workloads/junit/fw_cfg.xml"
[profile.ivshmem]
inherits = "integration"
default-filter = 'test(ivshmem::)'
junit.path = "/root/workloads/junit/ivshmem.xml"
[profile.common_cvm]
inherits = "integration"
default-filter = 'test(common_cvm::)'
junit.path = "/root/workloads/junit/cvm.xml"

25
.editorconfig Normal file
View File

@@ -0,0 +1,25 @@
# https://editorconfig.org/
#
# Hints for editors to assist with correct formatting as you type.
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
indent_size = 4
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
# Recommendation, not enforced.
max_line_length = 80
[Makefile]
indent_style = tab
# Inherited as default
# [*.sh]
# indent_size = 4
[{Cargo.lock,*.md,*.toml,*.yml,*.yaml}]
indent_size = 2

View File

@@ -8,6 +8,7 @@ updates:
interval: weekly
allow:
- dependency-name: "acpi_tables"
- dependency-name: "iommufd-ioctls"
- dependency-name: "kvm-bindings"
- dependency-name: "kvm-ioctls"
- dependency-name: "linux-loader"
@@ -37,8 +38,14 @@ updates:
interval: weekly
allow:
- dependency-type: all
cooldown:
default-days: 7
semver-major-days: 14
semver-minor-days: 7
semver-patch-days: 3
ignore:
- dependency-name: "acpi_tables"
- dependency-name: "iommufd-ioctls"
- dependency-name: "kvm-bindings"
- dependency-name: "kvm-ioctls"
- dependency-name: "linux-loader"

View File

@@ -1,16 +0,0 @@
name: Cloud Hypervisor Dependency Audit
on:
pull_request:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
jobs:
security_audit:
name: Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/audit@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,77 +0,0 @@
name: Cloud Hypervisor Build
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust:
- stable
- beta
- nightly
- "1.89.0"
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
steps:
- name: Code checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install musl-gcc
run: sudo apt install -y musl-tools
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- name: Build (default features)
run: cargo build --locked --bin cloud-hypervisor
- name: Build (kvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm"
- name: Build (default features + tdx)
run: cargo build --locked --bin cloud-hypervisor --features "tdx"
- name: Build (default features + dbus_api)
run: cargo build --locked --bin cloud-hypervisor --features "dbus_api"
- name: Build (default features + guest_debug)
run: cargo build --locked --bin cloud-hypervisor --features "guest_debug"
- name: Build (default features + pvmemcontrol)
run: cargo build --locked --bin cloud-hypervisor --features "pvmemcontrol"
- name: Build (default features + fw_cfg)
run: cargo build --locked --bin cloud-hypervisor --features "fw_cfg"
- name: Build (default features + ivshmem)
run: cargo build --locked --bin cloud-hypervisor --features "ivshmem"
- name: Build (mshv)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv"
- name: Build (sev_snp)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "sev_snp"
- name: Build (igvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "igvm"
- name: Build (mshv + kvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm"
- name: Release Build (default features)
run: cargo build --locked --all --release --target=${{ matrix.target }}
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"

890
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,890 @@
name: CI
on: [pull_request, merge_group]
permissions:
contents: read
pull-requests: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
cancel-in-progress: true
jobs:
preflight:
name: preflight
runs-on: ubuntu-latest
outputs:
full: ${{ steps.classify.outputs.full }}
rust: ${{ steps.changes.outputs.rust }}
cargo: ${{ steps.changes.outputs.cargo }}
openapi: ${{ steps.changes.outputs.openapi }}
dockerfile: ${{ steps.changes.outputs.dockerfile }}
shell: ${{ steps.changes.outputs.shell }}
ci: ${{ steps.changes.outputs.ci }}
docs: ${{ steps.changes.outputs.docs }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- id: changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
with:
filters: |
rust:
- '**/*.rs'
- 'build.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- 'rust-toolchain.toml'
cargo:
- '**/Cargo.toml'
- '**/Cargo.lock'
openapi:
- 'vmm/src/api/openapi/**'
dockerfile:
- 'resources/Dockerfile'
shell:
- '**/*.sh'
- 'scripts/**'
ci:
- '.github/workflows/**'
docs:
- 'docs/**'
- '**/*.md'
- '.github/ISSUE_TEMPLATE/**'
- 'LICENSES/**'
- 'CODEOWNERS'
- id: classify
name: Classify changes
run: |
set -eufo pipefail
full=false
if [[ "${{ steps.changes.outputs.rust }}" == "true" \
|| "${{ steps.changes.outputs.dockerfile }}" == "true" \
|| "${{ steps.changes.outputs.shell }}" == "true" \
|| "${{ steps.changes.outputs.ci }}" == "true" ]]; then
full=true
fi
echo "full=$full" >> "$GITHUB_OUTPUT"
echo "full=$full"
dco:
name: dco
needs: [preflight]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Set up Python 3.x
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Check DCO
if: github.event_name == 'pull_request'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eufo pipefail
pip3 install -U dco-check
dco-check -e "49699333+dependabot[bot]@users.noreply.github.com"
gitlint:
name: gitlint
needs: [preflight]
# PR-only: gitlint needs GITHUB_BASE_REF, unset on merge_group.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
# PR head, not the merge ref, so gitlint sees the PR's commits.
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Set up Python 3.10
uses: actions/setup-python@v6
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade gitlint
- name: Lint git commit messages
run: |
gitlint --commits "origin/$GITHUB_BASE_REF.."
lychee:
name: lychee
needs: [preflight]
if: needs.preflight.outputs.docs == 'true' || needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Get changed files in PR
id: changed-files
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
with:
base_sha: ${{ github.event.pull_request.base.sha }}
- name: Verify Changed Files
run: |
set -eufo pipefail
echo "--- tj-actions/changed-files Outputs ---"
echo "any_changed: ${{ steps.changed-files.outputs.any_changed }}"
echo "all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}"
echo "added_files: ${{ steps.changed-files.outputs.added_files }}"
echo "modified_files: ${{ steps.changed-files.outputs.modified_files }}"
echo "deleted_files: ${{ steps.changed-files.outputs.deleted_files }}"
echo "renamed_files: ${{ steps.changed-files.outputs.renamed_files }}"
echo "----------------------------------------"
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
echo "Detected changes: all_changed_files output is NOT empty."
else
echo "No changes detected: all_changed_files output IS empty."
fi
- name: Link Availability Check (Diff Only)
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
with:
args: --verbose --config .lychee.toml ${{ steps.changed-files.outputs.all_changed_files }}
failIfEmpty: false
fail: true
taplo:
name: taplo
needs: [preflight]
if: needs.preflight.outputs.cargo == 'true'
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install build dependencies
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
- name: Install taplo
run: cargo install taplo-cli --locked
- name: Check formatting
run: taplo fmt --check
audit:
name: audit
needs: [preflight]
if: needs.preflight.outputs.cargo == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions-rust-lang/audit@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
shlint:
name: shlint
needs: [preflight]
if: needs.preflight.outputs.shell == 'true' || needs.preflight.outputs.ci == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Run the shell script checkers
uses: luizm/action-sh-checker@883217215b11c1fabbf00eb1a9a041f62d74c744 # v0.10.0
env:
SHFMT_OPTS: -i 4 -d
SHELLCHECK_OPTS: -x --source-path scripts
hadolint:
name: hadolint
needs: [preflight]
if: needs.preflight.outputs.dockerfile == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Lint Dockerfile
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
with:
dockerfile: ./resources/Dockerfile
format: tty
no-fail: false
verbose: true
failure-threshold: info
reuse:
name: reuse
needs: [preflight]
if: needs.preflight.outputs.full == 'true' || needs.preflight.outputs.cargo == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: REUSE Compliance Check
uses: fsfe/reuse-action@v6
formatting:
name: formatting
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
rust: [nightly]
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-musl
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v7
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
components: rustfmt
- name: Formatting (rustfmt)
run: cargo fmt --all -- --check
- name: Formatting (fuzz) (rustfmt)
run: cargo fmt --all --manifest-path fuzz/Cargo.toml -- --check
package-consistency:
name: package-consistency
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install dependencies
run: sudo apt install -y python3
- name: Install Rust toolchain stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Check Rust VMM Package Consistency of root Workspace
run: python3 scripts/package-consistency-check.py github.com/rust-vmm
- name: Check Rust VMM Package Consistency of fuzz Workspace
run: |
set -eufo pipefail
pushd fuzz
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
popd
fuzz-build:
name: fuzz-build
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
rust: [nightly]
target: [x86_64-unknown-linux-gnu]
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v7
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- name: Install Cargo fuzz
run: cargo install cargo-fuzz
- name: Fuzz Build
run: cargo fuzz build
- name: Fuzz Check
run: cargo fuzz check
openapi:
name: openapi
needs: [preflight]
if: needs.preflight.outputs.openapi == 'true'
runs-on: ubuntu-latest
container: openapitools/openapi-generator-cli
steps:
- uses: actions/checkout@v7
- name: Validate OpenAPI
run: |
/usr/local/bin/docker-entrypoint.sh validate -i vmm/src/api/openapi/cloud-hypervisor.yaml
typos:
name: typos
needs: [preflight]
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0
quality:
name: quality
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
# Beta clippy is non-blocking; continue-on-error below keeps the
# aggregated needs.quality.result green when only beta fails.
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
rust:
- beta
- stable
target:
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
include:
- rust: beta
experimental: true
- rust: stable
experimental: false
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
components: clippy
- name: Bisectability Check (default features)
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
run: |
set -eufo pipefail
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
git checkout ${{ github.sha }}
- name: Clippy (kvm)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings
- name: Clippy (mshv)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings
- name: Clippy (mshv + kvm)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings
- name: Clippy (default features)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples -- -D warnings
- name: Clippy (default features + guest_debug)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings
- name: Clippy (default features + pvmemcontrol)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings
- name: Clippy (default features + tracing)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings
- name: Clippy (default features + fw_cfg)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings
- name: Clippy (default features + ivshmem)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings
- name: Clippy (kvm + sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm,sev_snp" -- -D warnings
- name: Clippy (mshv + sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,sev_snp" -- -D warnings
- name: Clippy (mshv + igvm + sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,igvm,sev_snp" -- -D warnings
- name: Clippy (kvm + igvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm,igvm" -- -D warnings
- name: Clippy (mshv + igvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,igvm" -- -D warnings
- name: Clippy (kvm + igvm + sev_snp + fw_cfg)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm,igvm,sev_snp,fw_cfg" -- -D warnings
- name: Clippy (default features + sev_snp + igvm + fw_cfg)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "sev_snp,igvm,fw_cfg" -- -D warnings
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
build:
name: build
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust:
- stable
- beta
- nightly
- "1.89.0" # MSRV — keep quoted.
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install musl-gcc
run: sudo apt install -y musl-tools
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- name: Build (default features)
run: cargo build --locked --bin cloud-hypervisor
- name: Build (kvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm"
- name: Build (default features + dbus_api)
run: cargo build --locked --bin cloud-hypervisor --features "dbus_api"
- name: Build (default features + guest_debug)
run: cargo build --locked --bin cloud-hypervisor --features "guest_debug"
- name: Build (default features + pvmemcontrol)
run: cargo build --locked --bin cloud-hypervisor --features "pvmemcontrol"
- name: Build (default features + fw_cfg)
run: cargo build --locked --bin cloud-hypervisor --features "fw_cfg"
- name: Build (default features + ivshmem)
run: cargo build --locked --bin cloud-hypervisor --features "ivshmem"
- name: Build (mshv)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv"
- name: Build (mshv + igvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,igvm"
- name: Build (mshv + sev_snp)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,sev_snp"
- name: Build (mshv + igvm + sev_snp)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,igvm,sev_snp"
- name: Build (kvm + sev_snp)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm,sev_snp"
- name: Build (kvm + igvm + sev_snp + fw_cfg)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm,igvm,sev_snp,fw_cfg"
- name: Build (kvm + igvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm,igvm"
- name: Build (mshv + kvm)
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm"
- name: Release Build (default features)
run: cargo build --locked --all --release --target=${{ matrix.target }}
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
build-riscv64:
name: build-riscv64
needs: [preflight]
if: needs.preflight.outputs.full == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust:
- stable
- "1.89.0" # MSRV — keep quoted.
env:
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install riscv64 cross linker
run: sudo apt-get update && sudo apt-get install -y gcc-riscv64-linux-gnu
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: riscv64gc-unknown-linux-gnu
- name: Build (kvm)
run: cargo build --locked --package cloud-hypervisor --no-default-features --features "kvm" --target riscv64gc-unknown-linux-gnu
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
# garm-jammy + gnu: runs on PR and MQ. Other 3 matrix entries are in
# integration-x86-64-mq (sibling, MQ-only, runs in parallel).
integration-x86-64-pr:
name: integration-x86-64-pr
needs: [preflight, dco, quality, build]
if: >-
needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 80
env:
# Our runner has 16 cores (nproc).
# We limit parallelism only to avoid exhausting disk space and memory
# resources, not to save CPU resources.
PARALLEL_INTEGRATION_TESTS_NUM: 12
runs-on: garm-jammy-16
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Docker
run: |
set -eufo pipefail
sudo apt-get update
sudo apt-get -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli
- name: Prepare for VDPA
run: scripts/prepare_vdpa.sh
- name: Run unit tests
run: scripts/dev_cli.sh tests --unit --libc gnu
- name: Load openvswitch module
run: sudo modprobe openvswitch
- name: Run integration tests
timeout-minutes: 60
run: scripts/dev_cli.sh tests --integration --libc gnu
# MQ-only: the 3 matrix entries that integration-x86-64-pr does not cover.
integration-x86-64-mq:
name: integration-x86-64-mq
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 80
env:
# Our runner has 16 cores (nproc).
# We limit parallelism only to avoid exhausting disk space and memory
# resources, not to save CPU resources.
PARALLEL_INTEGRATION_TESTS_NUM: 12
strategy:
fail-fast: false
matrix:
include:
- {runner: garm-jammy, libc: musl}
- {runner: garm-jammy-amd, libc: gnu}
- {runner: garm-jammy-amd, libc: musl}
# format() because `${{ matrix.runner }}-16` is not valid in runs-on.
runs-on: ${{ format('{0}-16', matrix.runner) }}
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Docker
run: |
set -eufo pipefail
sudo apt-get update
sudo apt-get -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli
- name: Prepare for VDPA
run: scripts/prepare_vdpa.sh
- name: Run unit tests
run: scripts/dev_cli.sh tests --unit --libc ${{ matrix.libc }}
- name: Load openvswitch module
run: sudo modprobe openvswitch
- name: Run integration tests
timeout-minutes: 60
run: scripts/dev_cli.sh tests --integration --libc ${{ matrix.libc }}
integration-arm64:
name: integration-arm64
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 120
env:
# Our runner has 80 cores (nproc).
# We limit parallelism only to avoid exhausting disk space and memory
# resources, not to save CPU resources.
PARALLEL_INTEGRATION_TESTS_NUM: 25
runs-on: bookworm-arm64
steps:
# arm64 runner user is "runner" (vfio's is "github-runner").
- name: Fix workspace permissions
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Run unit tests (musl)
run: scripts/dev_cli.sh tests --unit --libc musl
- name: Load openvswitch module
run: sudo modprobe openvswitch
- name: Run integration tests (musl)
timeout-minutes: 60
run: scripts/dev_cli.sh tests --integration --libc musl
- name: Install Azure CLI
run: |
set -eufo pipefail
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ bookworm main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
sudo apt update
sudo apt install -y azure-cli
- name: Download Windows image
shell: bash
run: |
set -eufo pipefail
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
IMG_PATH=$HOME/workloads/$IMG_BASENAME
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-25h2-6.raw.gz
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
pushd "$HOME/workloads"
if sha1sum "$IMG_BASENAME.sha1" --check; then
exit
fi
popd
mkdir -p "$HOME/workloads"
rm -f "$IMG_PATH" "$IMG_GZ_PATH"
az storage blob download --container-name private-images --file "$IMG_GZ_PATH" --name "$IMG_GZ_BLOB_NAME" --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
gzip -d "$IMG_GZ_PATH"
- name: Run Windows guest integration tests
timeout-minutes: 30
run: scripts/dev_cli.sh tests --integration-windows --libc musl
integration-vfio:
name: integration-vfio
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
runs-on: vfio-nvidia
env:
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
steps:
# vfio-nvidia runner user is "github-runner" (not "runner" like arm64).
- name: Fix workspace permissions
run: sudo chown -R github-runner:github-runner "${GITHUB_WORKSPACE}"
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Run VFIO integration tests
timeout-minutes: 25
run: scripts/dev_cli.sh tests --integration-vfio
# Most tests are failing with musl, see #6790
# - name: Run VFIO integration tests for musl
# timeout-minutes: 25
# run: scripts/dev_cli.sh tests --integration-vfio --libc musl
integration-windows:
name: integration-windows
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
runs-on: garm-jammy-16
steps:
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Docker
run: |
set -eufo pipefail
sudo apt-get update
sudo apt-get -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli
- name: Install Azure CLI
run: |
set -eufo pipefail
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
sudo apt update
sudo apt install -y azure-cli
- name: Download Windows image
run: |
set -eufo pipefail
mkdir $HOME/workloads
az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2025-amd64-1.raw" --name windows-server-2025-amd64-1.raw --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
- name: Run Windows guest integration tests
timeout-minutes: 15
run: scripts/dev_cli.sh tests --integration-windows
- name: Run Windows guest integration tests for musl
timeout-minutes: 15
run: scripts/dev_cli.sh tests --integration-windows --libc musl
integration-mshv-x86-64:
name: integration-mshv-x86-64
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 35
runs-on: mshv
steps:
# mshv runner user is "lsgunner"
- name: Fix workspace and Docker socket permissions
run: |
sudo chown -R lsgrunner:lsgrunner ${GITHUB_WORKSPACE}
sudo chmod 666 /var/run/docker.sock
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Prepare for VDPA
run: scripts/prepare_vdpa.sh
- name: Run integration tests
timeout-minutes: 30
run: scripts/dev_cli.sh tests --integration
# Rate-limiter host is not available
# integration-rate-limiter:
# name: integration-rate-limiter
# needs: [preflight, dco, quality, build]
# if: >-
# github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
# runs-on: bare-metal-9950x
# env:
# AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
# steps:
# - name: Code checkout
# uses: actions/checkout@v7
# with:
# fetch-depth: 0
# - name: Run rate-limiter integration tests
# timeout-minutes: 20
# run: scripts/dev_cli.sh tests --integration-rate-limiter
integration-sev-snp:
name: integration-sev-snp
needs: [preflight, dco, quality, build]
if: >-
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
timeout-minutes: 30
runs-on: noble-sevsnp
steps:
# Self-hosted runners reuse their workdir; a previous privileged
# container run can leave root-owned files behind.
- name: Fix workspace permissions
run: sudo chown -R "$(id -un):$(id -gn)" "${GITHUB_WORKSPACE}"
- name: Code checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Sanity-check SEV-SNP prerequisites
run: |
set -eufo pipefail
echo "Checking hypervisor device nodes..."
test -e /dev/kvm || { echo "::error::/dev/kvm missing"; exit 1; }
test -e /dev/sev || { echo "::error::/dev/sev missing"; exit 1; }
echo "Checking staged IGVM/kernel artifacts..."
test -d /usr/share/cloud-hypervisor/cvm \
|| { echo "::error::/usr/share/cloud-hypervisor/cvm missing"; exit 1; }
ls -l /usr/share/cloud-hypervisor/cvm
- name: Run CVM (SEV-SNP) integration tests
timeout-minutes: 20
run: scripts/dev_cli.sh tests --integration-cvm --hypervisor kvm
# Rate-limiter host is not available
# integration-rate-limiter:
# name: integration-rate-limiter
# needs: [preflight, dco, quality, build]
# if: >-
# github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
# runs-on: bare-metal-9950x
# env:
# AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
# steps:
# - name: Code checkout
# uses: actions/checkout@v7
# with:
# fetch-depth: 0
# - name: Run rate-limiter integration tests
# timeout-minutes: 20
# run: scripts/dev_cli.sh tests --integration-rate-limiter
# The single required-status check. Branch protection requires this one job.
all-green:
name: all-green
needs:
- audit
- build
- build-riscv64
- dco
- formatting
- fuzz-build
- gitlint
- hadolint
- integration-arm64
- integration-sev-snp
- integration-vfio
- integration-mshv-x86-64
- integration-windows
- integration-x86-64-mq
- integration-x86-64-pr
- openapi
- package-consistency
- preflight
- quality
- reuse
- shlint
- taplo
- typos
if: always()
runs-on: ubuntu-latest
steps:
- name: Verify all dependencies succeeded or were skipped
env:
NEEDS_JSON: ${{ toJson(needs) }}
run: |
set -eufo pipefail
echo "$NEEDS_JSON" | jq .
# success or skipped = pass; failure or cancelled = red.
echo "$NEEDS_JSON" | jq -e '
to_entries
| map(select(.value.result != "success" and .value.result != "skipped"))
| length == 0
' >/dev/null

View File

@@ -1,20 +0,0 @@
name: DCO
on: [pull_request, merge_group]
jobs:
check:
name: DCO Check ("Signed-off-by")
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python 3.x
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Check DCO
if: ${{ github.event_name == 'pull_request' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pip3 install -U dco-check
dco-check -e "49699333+dependabot[bot]@users.noreply.github.com"

View File

@@ -6,7 +6,7 @@ on:
pull_request:
paths: resources/Dockerfile
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
cancel-in-progress: true
env:
@@ -14,52 +14,106 @@ env:
IMAGE_NAME: ${{ github.repository }}
jobs:
main:
build:
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
runs-on: ubuntu-latest
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Code checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login to ghcr
uses: docker/login-action@v3
if: ${{ github.event_name == 'push' }}
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
file: ./resources/Dockerfile
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name == 'push' }}
- name: Export digest
if: ${{ github.event_name == 'push' }}
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
if: ${{ github.event_name == 'push' }}
uses: actions/upload-artifact@v7
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs: build
if: ${{ github.event_name == 'push' }}
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# generate Docker tags based on the following events/attributes
tags: |
type=raw,value=20251114-0
type=raw,value=20260522-0
type=sha
- name: Build and push
if: ${{ github.event_name == 'push' }}
uses: docker/build-push-action@v6
- name: Login to ghcr
uses: docker/login-action@v4
with:
file: ./resources/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build only
if: ${{ github.event_name == 'pull_request' }}
uses: docker/build-push-action@v6
with:
file: ./resources/Dockerfile
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

View File

@@ -1,32 +0,0 @@
name: Cloud Hypervisor Code Formatting
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Code Formatting
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- nightly
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-musl
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v6
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
components: rustfmt
- name: Formatting (rustfmt)
run: cargo fmt --all -- --check
- name: Formatting (fuzz) (rustfmt)
run: cargo fmt --all --manifest-path fuzz/Cargo.toml -- --check

View File

@@ -1,32 +0,0 @@
name: Cloud Hypervisor Cargo Fuzz Build
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Cargo Fuzz Build
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- nightly
target:
- x86_64-unknown-linux-gnu
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v6
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- name: Install Cargo fuzz
run: cargo install cargo-fuzz
- name: Fuzz Build
run: cargo fuzz build
- name: Fuzz Check
run: cargo fuzz check

View File

@@ -1,25 +0,0 @@
name: Commit messages check
on:
pull_request:
jobs:
gitlint:
name: Check commit messages
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Set up Python 3.10
uses: actions/setup-python@v6
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade gitlint
- name: Lint git commit messages
run: |
gitlint --commits origin/$GITHUB_BASE_REF..

View File

@@ -1,25 +0,0 @@
name: Lint Dockerfile
on:
push:
paths:
- resources/Dockerfile
pull_request:
paths:
- resources/Dockerfile
jobs:
hadolint:
name: Run Hadolint Dockerfile Linter
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Lint Dockerfile
uses: hadolint/hadolint-action@master
with:
dockerfile: ./resources/Dockerfile
format: tty
no-fail: false
verbose: true
failure-threshold: info

View File

@@ -1,54 +0,0 @@
name: Cloud Hypervisor Tests (ARM64)
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
timeout-minutes: 120
name: Tests (ARM64)
runs-on: bookworm-arm64
steps:
- name: Fix workspace permissions
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
- name: Code checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Run unit tests (musl)
run: scripts/dev_cli.sh tests --unit --libc musl
- name: Load openvswitch module
run: sudo modprobe openvswitch
- name: Run integration tests (musl)
timeout-minutes: 60
run: scripts/dev_cli.sh tests --integration --libc musl
- name: Install Azure CLI
if: ${{ github.event_name != 'pull_request' }}
run: |
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ bookworm main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
sudo apt update
sudo apt install -y azure-cli
- name: Download Windows image
if: ${{ github.event_name != 'pull_request' }}
shell: bash
run: |
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
IMG_PATH=$HOME/workloads/$IMG_BASENAME
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
pushd "$HOME/workloads"
if sha1sum "$IMG_BASENAME.sha1" --check; then
exit
fi
popd
mkdir -p "$HOME/workloads"
az storage blob download --container-name private-images --file "$IMG_GZ_PATH" --name "$IMG_GZ_BLOB_NAME" --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
gzip -d $IMG_GZ_PATH
- name: Run Windows guest integration tests
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 30
run: scripts/dev_cli.sh tests --integration-windows --libc musl

View File

@@ -7,16 +7,26 @@ on:
jobs:
build:
name: Tests (Metrics)
runs-on: bare-metal-9950x
runs-on: garm-jammy-16
env:
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
steps:
- name: Code checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Docker
run: |
set -eufo pipefail
sudo apt-get update
sudo apt-get -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli
- name: Run metrics tests
timeout-minutes: 60
run: scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json
run: scripts/dev_cli.sh tests --metrics -- --test-exclude micro_,block_qcow2 -- --report-file /root/workloads/metrics.json
- name: Upload metrics report
run: 'curl -X PUT https://ch-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'

View File

@@ -1,25 +0,0 @@
name: Cloud Hypervisor Tests (Rate-Limiter)
on: [merge_group, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Tests (Rate-Limiter)
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'bare-metal-9950x' }}
env:
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
steps:
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Run rate-limiter integration tests
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 20
run: scripts/dev_cli.sh tests --integration-rate-limiter
- name: Skipping build for PR
if: ${{ github.event_name == 'pull_request' }}
run: echo "Skipping build for PR"

View File

@@ -1,33 +0,0 @@
name: Cloud Hypervisor Tests (VFIO)
on: [merge_group, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Tests (VFIO)
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'vfio-nvidia' }}
env:
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
steps:
- name: Fix workspace permissions
if: ${{ github.event_name != 'pull_request' }}
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Run VFIO integration tests
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 15
run: scripts/dev_cli.sh tests --integration-vfio
# Most tests are failing with musl see #6790
# - name: Run VFIO integration tests for musl
# if: ${{ github.event_name != 'pull_request' }}
# timeout-minutes: 15
# run: scripts/dev_cli.sh tests --integration-vfio --libc musl
- name: Skipping build for PR
if: ${{ github.event_name == 'pull_request' }}
run: echo "Skipping build for PR"

View File

@@ -1,50 +0,0 @@
name: Cloud Hypervisor Tests (Windows Guest)
on: [merge_group, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Tests (Windows Guest)
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'garm-jammy-16' }}
steps:
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Docker
if: ${{ github.event_name != 'pull_request' }}
run: |
sudo apt-get update
sudo apt-get -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli
- name: Install Azure CLI
if: ${{ github.event_name != 'pull_request' }}
run: |
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
sudo apt update
sudo apt install -y azure-cli
- name: Download Windows image
if: ${{ github.event_name != 'pull_request' }}
run: |
mkdir $HOME/workloads
az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
- name: Run Windows guest integration tests
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 15
run: scripts/dev_cli.sh tests --integration-windows
- name: Run Windows guest integration tests for musl
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 15
run: scripts/dev_cli.sh tests --integration-windows --libc musl
- name: Skipping build for PR
if: ${{ github.event_name == 'pull_request' }}
run: echo "Skipping build for PR"

View File

@@ -1,52 +0,0 @@
name: Cloud Hypervisor Tests (x86-64)
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
runner: ['garm-jammy', "garm-jammy-amd"]
libc: ["musl", 'gnu']
name: Tests (x86-64)
runs-on: ${{ github.event_name == 'pull_request' && !(matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') && 'ubuntu-latest' || format('{0}-16', matrix.runner) }}
steps:
- name: Code checkout
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Docker
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
run: |
sudo apt-get update
sudo apt-get -y install ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli
- name: Prepare for VDPA
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
run: scripts/prepare_vdpa.sh
- name: Run unit tests
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
run: scripts/dev_cli.sh tests --unit --libc ${{ matrix.libc }}
- name: Load openvswitch module
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
run: sudo modprobe openvswitch
- name: Run integration tests
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
timeout-minutes: 40
run: scripts/dev_cli.sh tests --integration --libc ${{ matrix.libc }}
- name: Run live-migration integration tests
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
timeout-minutes: 20
run: scripts/dev_cli.sh tests --integration-live-migration --libc ${{ matrix.libc }}
- name: Skipping build for PR
if: ${{ github.event_name == 'pull_request' && matrix.runner != 'garm-jammy' && matrix.libc != 'gnu' }}
run: echo "Skipping build for PR"

View File

@@ -1,45 +0,0 @@
name: Link Check (lychee)
on: pull_request
jobs:
link_check:
name: Link Check
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v6
with:
# Fetch the entire history so git diff can compare against the base branch
fetch-depth: 0
- name: Get changed files in PR
id: changed-files
uses: tj-actions/changed-files@v47 # Using a dedicated action for robustness
with:
# Compare the HEAD of the PR with the merge-base (where the PR branches off)
base_sha: ${{ github.event.pull_request.base.sha }}
# NEW STEP: Print all changed-files outputs for verification
- name: Verify Changed Files
run: |
echo "--- tj-actions/changed-files Outputs ---"
echo "any_changed: ${{ steps.changed-files.outputs.any_changed }}"
echo "all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}"
echo "added_files: ${{ steps.changed-files.outputs.added_files }}"
echo "modified_files: ${{ steps.changed-files.outputs.modified_files }}"
echo "deleted_files: ${{ steps.changed-files.outputs.deleted_files }}"
echo "renamed_files: ${{ steps.changed-files.outputs.renamed_files }}"
echo "----------------------------------------"
# This will also show if the all_changed_files string is empty or not
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
echo "Detected changes: all_changed_files output is NOT empty."
else
echo "No changes detected: all_changed_files output IS empty."
fi
- name: Link Availability Check (Diff Only)
# MODIFIED: Only run lychee if the 'all_changed_files' output is not an empty string
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
uses: lycheeverse/lychee-action@master
with:
# Pass the space-separated list of changed files to lychee
args: --verbose --config .lychee.toml ${{ steps.changed-files.outputs.all_changed_files }}
failIfEmpty: false
fail: true

View File

@@ -1,249 +0,0 @@
name: MSHV Infra Setup
on:
workflow_call:
inputs:
ARCH:
description: 'Architecture for the VM'
required: true
type: string
KEY:
description: 'SSH Key Name'
required: true
type: string
OS_DISK_SIZE:
description: 'OS Disk Size in GB'
required: true
type: string
RG:
description: 'Resource Group Name'
required: true
type: string
VM_SKU:
description: 'VM SKU'
required: true
type: string
secrets:
MI_CLIENT_ID:
required: true
RUNNER_RG:
required: true
STORAGE_ACCOUNT_PATHS:
required: true
ARCH_SOURCE_PATH:
required: true
USERNAME:
required: true
outputs:
RG_NAME:
description: 'Resource group of the VM'
value: ${{ jobs.infra-setup.outputs.RG_NAME }}
VM_NAME:
description: 'Name of the VM'
value: ${{ jobs.infra-setup.outputs.VM_NAME }}
PRIVATE_IP:
description: 'Private IP of the VM'
value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
infra-setup:
name: ${{ inputs.ARCH }} VM Provision
runs-on: mshv
continue-on-error: true
outputs:
RG_NAME: ${{ steps.rg-setup.outputs.RG_NAME }}
VM_NAME: ${{ steps.vm-setup.outputs.VM_NAME }}
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
steps:
- name: Install & login to AZ CLI
env:
MI_CLIENT_ID: ${{ secrets.MI_CLIENT_ID }}
run: |
set -e
echo "Installing Azure CLI if not already installed"
if ! command -v az &>/dev/null; then
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
else
echo "Azure CLI already installed"
fi
az --version
echo "Logging into Azure CLI using Managed Identity"
az login --identity --client-id ${MI_CLIENT_ID}
- name: Get Location
id: get-location
env:
SKU: ${{ inputs.VM_SKU }}
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
run: |
set -e
# Extract vCPU count from SKU (e.g., "Standard_D2s_v3" => 2)
vcpu=$(echo "$SKU" | sed -n 's/^Standard_[A-Za-z]\+\([0-9]\+\).*/\1/p')
if [[ -z "$vcpu" ]]; then
echo "Cannot extract vCPU count from SKU: $SKU"
exit 1
fi
SUPPORTED_LOCATIONS=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[] | .key')
for location in $SUPPORTED_LOCATIONS; do
family=$(az vm list-skus --size "$SKU" --location "$location" --resource-type "virtualMachines" --query '[0].family' -o tsv)
if [[ -z "$family" ]]; then
echo "Cannot determine VM family for SKU: $SKU in $location"
continue
fi
usage=$(az vm list-usage --location "$location" --query "[?name.value=='$family'] | [0]" -o json)
current=$(echo "$usage" | jq -r '.currentValue')
limit=$(echo "$usage" | jq -r '.limit')
if [[ $((limit - current)) -ge $vcpu ]]; then
echo "Sufficient quota found in $location"
echo "location=$location" >> "$GITHUB_OUTPUT"
exit 0
fi
done
echo "No location found with sufficient vCPU quota for SKU: $SKU"
exit 1
- name: Create Resource Group
id: rg-setup
env:
LOCATION: ${{ steps.get-location.outputs.location }}
RG: ${{ inputs.RG }}
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
run: |
set -e
echo "Creating Resource Group: $RG"
# Create the resource group
echo "Creating resource group in location: ${LOCATION}"
az group create --name ${RG} --location ${LOCATION}
echo "RG_NAME=${RG}" >> $GITHUB_OUTPUT
echo "Resource group created successfully."
- name: Generate SSH Key
id: generate-ssh-key
env:
KEY: ${{ inputs.KEY }}
run: |
set -e
echo "Generating SSH key: $KEY"
mkdir -p ~/.ssh
ssh-keygen -t rsa -b 4096 -f ~/.ssh/${KEY} -N ""
- name: Create VM
id: vm-setup
env:
KEY: ${{ inputs.KEY }}
LOCATION: ${{ steps.get-location.outputs.location }}
OS_DISK_SIZE: ${{ inputs.OS_DISK_SIZE }}
RG: ${{ inputs.RG }}
RUNNER_RG: ${{ secrets.RUNNER_RG }}
USERNAME: ${{ secrets.USERNAME }}
VM_SKU: ${{ inputs.VM_SKU }}
VM_IMAGE_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_image
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
run: |
set -e
echo "Creating $VM_SKU VM: $VM_NAME"
# Extract subnet ID from the runner VM
echo "Retrieving subnet ID..."
SUBNET_ID=$(az network vnet list --resource-group ${RUNNER_RG} --query "[?contains(location, '${LOCATION}')].{SUBNETS:subnets}" | jq -r ".[0].SUBNETS[0].id")
if [[ -z "${SUBNET_ID}" ]]; then
echo "ERROR: Failed to retrieve Subnet ID."
exit 1
fi
# Extract image ID from the runner VM
echo "Retrieving image ID..."
IMAGE_ID=$(az image show --resource-group ${RUNNER_RG} --name ${VM_IMAGE_NAME} --query "id" -o tsv)
if [[ -z "${IMAGE_ID}" ]]; then
echo "ERROR: Failed to retrieve Image ID."
exit 1
fi
# Create VM
az vm create \
--resource-group ${RG} \
--name ${VM_NAME} \
--subnet ${SUBNET_ID} \
--size ${VM_SKU} \
--location ${LOCATION} \
--image ${IMAGE_ID} \
--os-disk-size-gb ${OS_DISK_SIZE} \
--public-ip-sku Standard \
--storage-sku Premium_LRS \
--public-ip-address "" \
--admin-username ${USERNAME} \
--ssh-key-value ~/.ssh/${KEY}.pub \
--security-type Standard \
--output json
az vm boot-diagnostics enable --name ${VM_NAME} --resource-group ${RG}
echo "VM_NAME=${VM_NAME}" >> $GITHUB_OUTPUT
echo "VM creation process completed successfully."
- name: Get VM Private IP
id: get-vm-ip
env:
RG: ${{ inputs.RG }}
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
run: |
set -e
echo "Retrieving VM Private IP address..."
# Retrieve VM Private IP address
PRIVATE_IP=$(az vm show -g ${RG} -n ${VM_NAME} -d --query privateIps -o tsv)
if [[ -z "$PRIVATE_IP" ]]; then
echo "ERROR: Failed to retrieve private IP address."
exit 1
fi
echo "PRIVATE_IP=$PRIVATE_IP" >> $GITHUB_OUTPUT
- name: Wait for SSH availability
env:
KEY: ${{ inputs.KEY }}
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
USERNAME: ${{ secrets.USERNAME }}
run: |
echo "Waiting for SSH to be accessible..."
timeout 120 bash -c 'until ssh -o StrictHostKeyChecking=no -i ~/.ssh/${KEY} ${USERNAME}@${PRIVATE_IP} "exit" 2>/dev/null; do sleep 5; done'
echo "VM is accessible!"
- name: Remove Old Host Key
env:
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
run: |
set -e
echo "Removing the old host key"
ssh-keygen -R $PRIVATE_IP
- name: SSH into VM and Install Dependencies
env:
KEY: ${{ inputs.KEY }}
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
USERNAME: ${{ secrets.USERNAME }}
run: |
set -e
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
set -e
echo "Logged in successfully."
echo "Installing dependencies..."
sudo tdnf install -y git moby-engine moby-cli clang llvm pkg-config make gcc glibc-devel
echo "Installing Rust..."
curl -sSf https://sh.rustup.rs | sh -s -- --default-toolchain stable --profile default -y
export PATH="\$HOME/.cargo/bin:\$PATH"
cargo --version
sudo mkdir -p /etc/docker/
echo '{"default-ulimits":{"nofile":{"Hard":65535,"Name":"nofile","Soft":65535}}}' | sudo tee /etc/docker/daemon.json
sudo systemctl stop docker
sudo systemctl enable docker.service
sudo systemctl enable containerd.service
sudo systemctl start docker
sudo groupadd -f docker
sudo usermod -a -G docker ${USERNAME}
sudo systemctl restart docker
EOF

View File

@@ -1,131 +0,0 @@
name: Cloud Hypervisor Tests (MSHV) (x86_64)
on: [pull_request_target, merge_group]
jobs:
infra-setup:
name: MSHV Infra Setup (x86_64)
uses: ./.github/workflows/mshv-infra.yaml
with:
ARCH: x86_64
KEY: azure_key_${{ github.run_id }}
OS_DISK_SIZE: 512
RG: MSHV-INTEGRATION-${{ github.run_id }}
VM_SKU: Standard_D16s_v5
secrets:
MI_CLIENT_ID: ${{ secrets.MSHV_MI_CLIENT_ID }}
RUNNER_RG: ${{ secrets.MSHV_RUNNER_RG }}
STORAGE_ACCOUNT_PATHS: ${{ secrets.MSHV_STORAGE_ACCOUNT_PATHS }}
ARCH_SOURCE_PATH: ${{ secrets.MSHV_X86_SOURCE_PATH }}
USERNAME: ${{ secrets.MSHV_USERNAME }}
run-tests:
name: Integration Tests (x86_64)
needs: infra-setup
if: ${{ always() && needs.infra-setup.result == 'success' }}
runs-on: mshv
continue-on-error: true
steps:
- name: Run integration tests
timeout-minutes: 60
env:
KEY: azure_key_${{ github.run_id }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO_URL: https://github.com/cloud-hypervisor/cloud-hypervisor.git
REPO_DIR: cloud-hypervisor
PRIVATE_IP: ${{ needs.infra-setup.outputs.PRIVATE_IP }}
RG: MSHV-${{ github.run_id }}
USERNAME: ${{ secrets.MSHV_USERNAME }}
run: |
set -e
echo "Connecting to the VM via SSH..."
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
set -e
echo "Logged in successfully."
export PATH="\$HOME/.cargo/bin:\$PATH"
if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then
git clone --depth 1 "$REPO_URL" "$REPO_DIR"
cd "$REPO_DIR"
git fetch origin pull/${{ github.event.pull_request.number }}/merge
git checkout FETCH_HEAD
else
git clone --depth 1 --single-branch --branch "${{ github.ref_name }}" "$REPO_URL" "$REPO_DIR"
cd "$REPO_DIR"
fi
echo "Loading VDPA kernel modules..."
sudo modprobe vdpa
sudo modprobe vhost_vdpa
sudo modprobe vdpa_sim
sudo modprobe vdpa_sim_blk
sudo modprobe vdpa_sim_net
echo "Creating VDPA devices..."
sudo vdpa dev add name vdpa-blk0 mgmtdev vdpasim_blk
sudo vdpa dev add name vdpa-blk1 mgmtdev vdpasim_blk
sudo vdpa dev add name vdpa-blk2 mgmtdev vdpasim_net
echo "Setting permissions..."
for i in 0 1 2; do
dev="/dev/vhost-vdpa-$i"
if [ -e "$dev" ]; then
sudo chown $USER:$USER "$dev"
sudo chmod 660 "$dev"
else
echo "Warning: Device $dev not found"
fi
done
sudo ./scripts/dev_cli.sh tests --hypervisor mshv --integration
EOF
- name: Dump dmesg
if: always()
continue-on-error: true
env:
KEY: azure_key_${{ github.run_id }}
PRIVATE_IP: ${{ needs.infra-setup.outputs.PRIVATE_IP }}
USERNAME: ${{ secrets.MSHV_USERNAME }}
run: |
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
sudo dmesg
EOF
- name: Dump serial console logs
if: always()
continue-on-error: true
env:
RG_NAME: ${{ needs.infra-setup.outputs.RG_NAME }}
VM_NAME: ${{ needs.infra-setup.outputs.VM_NAME }}
run: |
set -e
az vm boot-diagnostics get-boot-log --name "${VM_NAME}" --resource-group "${RG_NAME}" | jq -r
cleanup:
name: Cleanup
needs: run-tests
if: always()
runs-on: mshv
steps:
- name: Delete RG
env:
RG: MSHV-INTEGRATION-${{ github.run_id }}
run: |
if az group exists --name ${RG}; then
az group delete --name ${RG} --yes --no-wait
else
echo "Resource Group ${RG} does not exist. Skipping deletion."
fi
echo "Cleanup process completed."
- name: Delete SSH Key
env:
KEY: azure_key_${{ github.run_id }}
run: |
if [ -f ~/.ssh/${KEY} ]; then
rm -f ~/.ssh/${KEY} ~/.ssh/${KEY}.pub
echo "SSH key deleted successfully."
else
echo "SSH key does not exist. Skipping deletion."
fi
echo "Cleanup process completed."

View File

@@ -1,14 +0,0 @@
name: Cloud Hypervisor OpenAPI Validation
on: [pull_request, merge_group]
jobs:
Validate:
runs-on: ubuntu-latest
container: openapitools/openapi-generator-cli
steps:
- uses: actions/checkout@v6
- name: Validate OpenAPI
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
/usr/local/bin/docker-entrypoint.sh validate -i vmm/src/api/openapi/cloud-hypervisor.yaml

View File

@@ -1,32 +0,0 @@
name: Cloud Hypervisor Consistency
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Rust VMM Consistency Check
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install dependencies
run: sudo apt install -y python3
- name: Install Rust toolchain stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Check Rust VMM Package Consistency of root Workspace
run: python3 scripts/package-consistency-check.py github.com/rust-vmm
- name: Check Rust VMM Package Consistency of fuzz Workspace
run: |
pushd fuzz
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
popd

View File

@@ -1,30 +0,0 @@
name: Cloud Hypervisor RISC-V 64-bit kvm build Preview
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Cargo
runs-on: riscv64-qemu-host
strategy:
fail-fast: false
steps:
- name: Code checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Rust toolchain
run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0
- name: Build test (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo build --locked --no-default-features --features "kvm" -p cloud-hypervisor
- name: Clippy test (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm" -p cloud-hypervisor
- name: Check no files were modified
run: test -z "$(git status --porcelain)"

View File

@@ -1,39 +0,0 @@
name: Cloud Hypervisor RISC-V 64-bit Preview
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Cargo
runs-on: riscv64-qemu-host
strategy:
fail-fast: false
matrix:
module:
- hypervisor
- arch
- vm-allocator
- devices
steps:
- name: Code checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Rust toolchain
run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0
- name: Build ${{ matrix.module }} Module (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo build --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
- name: Clippy ${{ matrix.module }} Module (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings
- name: Test ${{ matrix.module }} Module (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo test --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
- name: Check no files were modified
run: test -z "$(git status --porcelain)"

View File

@@ -1,170 +0,0 @@
name: Cloud Hypervisor Quality Checks
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Quality (clippy)
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
rust:
- beta
- stable
target:
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
include:
- rust: beta
experimental: true
- rust: stable
experimental: false
steps:
- name: Code checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Rust toolchain (${{ matrix.rust }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
components: clippy
- name: Bisectability Check (default features)
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
run: |
set -e
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
git checkout ${{ github.sha }}
- name: Clippy (kvm)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings
- name: Clippy (mshv)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings
- name: Clippy (mshv + kvm)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings
- name: Clippy (default features)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples -- -D warnings
- name: Clippy (default features + guest_debug)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings
- name: Clippy (default features + pvmemcontrol)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings
- name: Clippy (default features + tracing)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings
- name: Clippy (default features + fw_cfg)
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings
- name: Clippy (default features + ivshmem)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings
- name: Clippy (sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings
- name: Clippy (igvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings
- name: Clippy (kvm + tdx)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
typos:
if: github.event_name == 'pull_request'
name: Typos / Spellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
# Executes "typos ."
- uses: crate-ci/typos@v1.40.0

View File

@@ -1,7 +1,7 @@
name: Cloud Hypervisor Release
on: [create, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
cancel-in-progress: true
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Install musl-gcc
if: contains(matrix.platform.target, 'musl')
run: sudo apt install -y musl-tools
@@ -54,7 +54,7 @@ jobs:
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
- name: Upload Release Artifacts
if: github.event_name == 'create' && github.event.ref_type == 'tag'
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: Artifacts for ${{ matrix.platform.target }}
path: |
@@ -80,13 +80,13 @@ jobs:
github.event_name == 'create' && github.event.ref_type == 'tag' &&
matrix.platform.target == 'x86_64-unknown-linux-gnu'
id: upload-release-cloud-hypervisor-vendored-sources
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
- name: Create GitHub Release
if: github.event_name == 'create' && github.event.ref_type == 'tag'
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
draft: true
files: |

View File

@@ -1,12 +0,0 @@
name: REUSE Compliance Check
on: [push, pull_request]
jobs:
reuse:
name: REUSE Compliance Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: REUSE Compliance Check
uses: fsfe/reuse-action@v6

View File

@@ -1,20 +0,0 @@
name: Shell scripts check
on:
pull_request:
merge_group:
push:
branches:
- main
jobs:
sh-checker:
name: Check shell scripts
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Run the shell script checkers
uses: luizm/action-sh-checker@master
env:
SHFMT_OPTS: -i 4 -d
SHELLCHECK_OPTS: -x --source-path scripts

View File

@@ -1,21 +0,0 @@
name: Cargo.toml Formatting (taplo)
on:
pull_request:
paths:
- '**/Cargo.toml'
jobs:
cargo_toml_format:
name: Cargo.toml Formatting
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install build dependencies
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
- name: Install taplo
run: cargo install taplo-cli --locked
- name: Check formatting
run: taplo fmt --check

11
.gitignore vendored
View File

@@ -1,10 +1,13 @@
/build
/.cargo
/target
**/*.rs.bk
**/Cargo.lock
**/rusty-tags.vi
/rpm/SOURCES
/.agents
/.cargo
/.claude
/.codex
/.vscode
/build
/rpm/SOURCES
/target
/vendor
__pycache__

View File

@@ -1,27 +1,36 @@
verbose = "info"
exclude_path = [".lychee.toml"]
exclude = [
# Availability of links below should be manually verified.
# Page for intel TDX support, returns 403 while querying.
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
# Page for TPM, returns 403 while querying.
'^https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf',
# GitHub user smibarber referenced in `CREDITS.md` no longer exist
'^https://github.com/smibarber',
# OSDev has added bot protection and accesses my result in 403 Forbidden.
'^https://wiki.osdev.org',
# Exclude all pages with $ in the URL since $XXX is a variable
"\\$.*",
# Exclude local files
"file://.*",
# Availability of links below should be manually verified.
# Page for intel TDX support, returns 403 while querying.
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
# Page for TPM, returns 403 while querying.
'^https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf',
# GitHub user smibarber referenced in `CREDITS.md` no longer exist
'^https://github.com/smibarber',
# OSDev has added bot protection and accesses my result in 403 Forbidden.
'^https://wiki.osdev.org',
# Exclude all pages with $ in the URL since $XXX is a variable
"\\$.*",
# Exclude local files
"file://.*",
# ARM documentation returns 403 Forbidden for automated CI checks.
'^http://infocenter\.arm\.com',
'^https://developer\.arm\.com',
# Ignore internal/unsupported protocols seen in logs
'^tcp://192\.168\.1\.10',
# Slack invite endpoints reject automated GETs and return 403.
'^https://join\.slack\.com/t/',
# Metrics publish endpoint only answers authenticated PUTs; a plain GET
# returns 404.
'^https://ch-metrics\.azurewebsites\.net/api/publishmetrics',
]
# Exclude loopback addresses
exclude_loopback = true
max_retries = 3
retry_wait_time = 5

View File

@@ -7,6 +7,6 @@ Files: docs/*.md *.md
Copyright: 2024
License: CC-BY-4.0
Files: scripts/* test_data/* *.toml .git* fuzz/Cargo.lock fuzz/.gitignore resources/linux-config-* vmm/src/api/openapi/cloud-hypervisor.yaml CODEOWNERS Cargo.lock
Files: scripts/* test_data/* *.toml .git* .editorconfig fuzz/Cargo.lock fuzz/.gitignore resources/linux-config-* vmm/src/api/openapi/cloud-hypervisor.yaml CODEOWNERS Cargo.lock
Copyright: 2024
License: Apache-2.0

View File

@@ -1,5 +1,6 @@
include = ["**/Cargo.toml"]
[formatting]
indent_string = " " # 2 spaces: keep in sync with .editorconfig
reorder_arrays = true
reorder_keys = true

View File

@@ -2,8 +2,8 @@
[files]
extend-exclude = [
"hypervisor/src/kvm/x86_64/mod.rs",
"resources/linux-config-*",
"hypervisor/src/kvm/x86_64/mod.rs",
"resources/linux-config-*",
]
[default.extend-words]
@@ -21,5 +21,8 @@ liness = "liness"
outout = "outout"
[default.extend-identifiers]
consts = "consts"
fo = "fo"
fpr = "fpr"
# Public Linux API
msg_controllen = "msg_controllen"

79
AGENTS.md Normal file
View File

@@ -0,0 +1,79 @@
## For Humans
This is a compact [AGENTS.md](https://agents.md/) file for Cloud Hypervisor.
It is meant to help automated coding agents make useful changes that stay safe,
reviewable, and compatible with the project's normal engineering constraints.
## For LLMs
### Project Context
- Start with `README.md` for the project shape and `CONTRIBUTING.md` for the
contribution rules, coding style, commit message guidance, and LLM assistance
disclosure policy. Following `CONTRIBUTING.md` is crucial!
- Respect `.editorconfig` when editing files, in addition to any
language-specific formatter required by `CONTRIBUTING.md`.
### Change Guidelines
- Prefer correctness, safety, and readability over micro-optimizations. Keep
changes small, reviewable, and aligned with the existing crate/module
boundaries. Avoid speculative changes and unrelated refactoring.
- For API, config, migration, device model, or hypervisor boundary changes,
consider the effect on all architectures and all backends. Changes to one
backend can be okay if the other backend still functions properly and could
be extended or modified later.
- Follow Rust best practices and the style already present in the touched code.
- Avoid new dependencies unless the benefit is clear and local alternatives are
not enough.
- Preserve existing behavior unless the requested change explicitly needs a
behavior change; refactors must preserve behavior. Call out compatibility or
migration implications.
- Do not invent APIs, behavior, or requirements. If something is uncertain,
state the uncertainty and proceed only with minimal, explicit assumptions.
- For `thiserror`-style errors, start messages with a capital letter and keep
the outer `Display` text short. Put all non-`#[source]` attributes in the
message to improve helpfulness, but do not repeat a `#[source]` value
inline: Cloud Hypervisor prints the full error chain, so only include the
concrete failure text directly when there is no source to report.
### Safety and Domain Notes
- Prefer safe Rust. If `unsafe` is necessary, keep it narrow, add a `SAFETY:`
comment with the invariants, and make sure the surrounding code upholds them.
- Assume concurrency matters. Avoid races, unsynchronized shared state, and
implicit ordering assumptions; prefer clear ownership and synchronization.
### Build and Test Notes
- Some workspace members require the `kvm` feature to build or test correctly.
When a default build failure looks feature-related, retry the narrow command
with `--features kvm` before widening the diagnosis.
- Prefer narrow crate/test commands while iterating, then broaden verification
when the touched surface justifies it.
- Formatting currently needs nightly-only rustfmt features; use
`cargo +nightly fmt --all`.
- Add targeted unit tests for bug fixes and non-trivial logic where practical.
Keep test scaffolding minimal and focused.
- Integration tests live in `./cloud-hypervisor/tests/` and are normally driven
by `./scripts/dev_cli.sh` / `./scripts/run_integration_tests_*.sh`. They need
host privileges, workloads, and container setup. To build the integration-test
code directly without the infrastructure from `./scripts`, set the Rust cfg
`devcli_testenv` or simply build through `clippy` which automatically includes
these code paths; otherwise the integration-test code is not included.
### Commit and Patch Formatting
- Follow the rules in `CONTRIBUTING.md`, including reviewable commit structure,
valid component prefixes, 72-column commit messages, and a `Signed-off-by`
trailer.
- Lines in a commit message that are allowed to exceed the 72-column limit are
specified in `./scripts/gitlint/rules`.
- For LLM-assisted changes, follow the disclosure guidance in `CONTRIBUTING.md`:
use the project's `Assisted-by:` trailer when disclosure is needed, and do not
add `Co-authored-by` or similar trailers unless that policy changes. Prefer
explicit version numbers, such as `Assisted-by: Claude:Opus-4.7`, rather than
`Assisted-by: Claude:Opus-4`.
- Temporary allowances such as `#[allow(unused)]` or ignored tests are only
acceptable if resolved within the same commit series or paired with a clear
TODO referencing a ticket. Ask the developer if in doubt.

View File

@@ -11,17 +11,52 @@ license of those projects.
New code should be under the [Apache v2
License](https://opensource.org/licenses/Apache-2.0).
## Coding Style
Cloud Hypervisor's main supported architectures are `x86_64` and `aarch64`,
and the main hypervisor backends are KVM and MSHV. `x86_64` with KVM gets the
most regular exercise, but changes should not make the other supported
architecture and backend combinations worse.
We follow the [Rust Style](https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src)
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`,
`clippy`, and other well-known code quality tool of the ecosystem for each submitted Pull Request (PR).
## Coding Style & Code Comments
We use the [Rust Style] guide and enforce formatting and linting in CI,
including `rustfmt`, `clippy`, and other common Rust quality checks, for every
pull request. We adapt to best practices, new lints and new tooling as the
ecosystem evolves.
Code should **speak for itself** (for example, by using descriptive identifiers)
and be **easy to read and maintain**. Beyond the conventions and tooling
described above, contributors have _some_ room to apply their own style and
preferred structure. Maintainers may still suggest refactorings where they
believe readability, consistency, or maintainability can be improved.
For new code, add documentation and comments where they **provide additional value**:
* **Rustdoc** explains the API to its users.
* **Inline comments** explain the code the reader, especially *why* it is
written that way.
* **Commit messages** explain the broader context of a change (for more
information on commit messages, see below).
Comments should be concise and add additional context or information to the code.
Logging should be minimal and high signal. Use `info!` for important normal
state changes that matter in production; use `warn!` or `error!` only for
abnormal conditions. Keep `debug!` for focused diagnostics. Please find more
information in [`docs/logging.md`](docs/logging.md).
Error messages should be sentence-style: start with a capital letter and stay
concise. For `thiserror`-style errors, put all non-`#[source]` attributes
(if they provide clear value) in the outer `Display` text to improve helpfulness,
but do not repeat a `#[source]` value there because Cloud Hypervisor prints the
full chain elsewhere.
[Rust Style]: https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src
## Basic Checks
```sh
# We currently rely on nightly-only formatting features
cargo +nightly fmt --all
cargo +nightly fmt --all
cargo check --all-targets --tests
cargo clippy --all-targets --tests
# Please note that this will not execute integration tests.
@@ -36,7 +71,7 @@ gitlint --commits "HEAD~3..HEAD"
_Caution: These tests are taking a long time to complete (40+ mins) and need special setup._
```sh
bash ./scripts/dev_cli.sh tests --integration -- --test-filter '<optionally filter test by name pattern>'
bash ./scripts/dev_cli.sh tests --integration -- --test-filter '<optionally filter test by name pattern>'
```
### Setup Commit Hook
@@ -58,42 +93,65 @@ commit you make.
## Certificate of Origin
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://web.archive.org/web/20230406041855/https://01.org/community/signed-process)
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://www.kernel.org/doc/Documentation/process/submitting-patches.rst)
used by the Linux kernel project.
## Patch format
## Patch format & Git Commit Hygiene
Beside the signed-off-by footer, we expect each patch to comply with the following format:
_We use **Patch** as synonym for **Commit**._
```
<component>: Change summary
We require patches to:
More detailed explanation of your changes: Why and how.
Wrap it to 72 characters.
See http://chris.beams.io/posts/git-commit/
for some more good pieces of advice.
- Have a `Signed-off-by: Name <email>` footer
- Follow the pattern: \
```
<component>: Change summary
Signed-off-by: <contributor@foo.com>
```
More detailed explanation of your changes: Why and how.
Wrap it to 72 characters.
See http://chris.beams.io/posts/git-commit/
for some more good pieces of advice.
For example:
Signed-off-by: <contributor@foo.com>
```
Valid components are listed in `TitleStartsWithComponent.py`. In short, each
cargo workspace member is a valid component as well as `build`, `ci`, `docs` and
`misc`.
Example patch:
```
vm-virtio: Reset underlying device on driver request
If the driver triggers a reset by writing zero into the status register
then reset the underlying device if supported. A device reset also
requires resetting various aspects of the queue.
In order to be able to do a subsequent reactivate it is required to
reclaim certain resources (interrupt and queue EventFDs.) If a device
reset is requested by the driver but the underlying device does not
support it then generate an error as the driver would not be able to
configure it anyway.
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
```
### Git Commit History
We value a clean, **reviewable** commit history. Each commit should represent
a self-contained, logical step that guides reviewers clearly from A to B.
Avoid patterns like `init A -> init B -> fix A` or \
`init design A -> revert A -> use design B`. Commits must be independently
reviewable - don't leave "fix previous commit" or earlier design attempts in
the history.
Intermediate work-in-progress changes are acceptable only if a subsequent
commit in the same series cleans them up (e.g. a temporary `#[allow(unused)]`
removed in the next commit).
## Pull requests
Cloud Hypervisor uses the “fork-and-pull” development model. Follow these steps if
@@ -104,10 +162,14 @@ you want to merge your changes to `cloud-hypervisor`:
1. Within your fork, create a branch for your contribution.
1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
against the main branch of the Cloud Hypervisor repository.
1. To update your pull request amend existing commits whenever applicable and
then push the new changes to your pull request branch.
1. Each commit must comply with the Commit Hygiene guidelines above.
1. A pull request should address a single component or concern to keep review
focused and approvals straightforward.
1. Once the pull request is approved it can be integrated.
Please squash any changes done during review already into the corresponding
commits instead of pushing `<component>: addressing review for A`-style commits.
## Issue tracking
If you have a problem, please let us know. We recommend using
@@ -123,26 +185,83 @@ comments or by adding the `Fixes` keyword to your commit message:
```
serial: Set terminal in raw mode
In order to have proper output from the serial, we need to setup the
terminal in raw mode. When the VM is shutting down, it is also the
VMM responsibility to set the terminal back into canonical mode if we
don't want to get any weird behavior from the shell.
Fixes #88
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
```
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
## AI Generated Code
## AI/LLM Assistance & Generated Code
Our policy is to decline any contributions known to contain contents
generated or derived from using Large Language Models (LLMs). This
includes ChatGPT, Gemini, Claude, Copilot and similar tools.
We recommend **a careful and conservative approach** to LLM usage, guided by
sound engineering judgment. Please use AI/LLM-assisted tooling thoughtfully and
responsibly to ensure efficient use of limited project resources, particularly
in code review and long-term maintenance. Our primary goals are to avoid
ambiguity in license compliance and to keep contributions clear and easy to
review.
The goal is to avoid ambiguity in license compliance and optimize the
use of limited project resources, especially for code review and
maintenance. This policy can be revisited as LLMs evolve and mature.
Or in other words: please apply common sense and don't blindly accept LLM
suggestions.
This policy can be revisited as LLMs evolve and mature.
### Code Review
We generally recommend doing early coarse-grained reviews using state-of-the-art
LLMs. This can help identify rough edges, copy & paste errors, and typos early
on. This reduces review cycles for human reviewers.
Please **do not** use GitHub Copilot directly in PRs to keep discussions clean.
Instead, ask an LLM of your choice for a review. A convenient way to do this is
- appending `.patch` to the GitHub PR URL
(e.g., `https://github.com/cloud-hypervisor/cloud-hypervisor/pull/1234.patch`)
and pasting it into the LLM of your choice, or
- using a local agent in your terminal, such as `codex` or `claude`.
### Contributions assisted by LLMs
All contributions **must** be submitted by a human contributor. Automated or
bot-driven PRs are not accepted.
You are responsible for every piece of code you submit, and you must understand
both the design and the implementation details. LLMs are useful for prototyping
and generating boilerplate code. However, large or complex logic must be
authored and fully understood by the contributor - LLM output should not be
submitted without careful review and comprehension.
Please disclose LLM use in your commit message and PR description if it
meaningfully contributed to the submitted code. Again, we recommend careful and
conservative use of LLMs, guided by common sense.
Use the following tag to disclose LLM assistance in your commit message:
```
Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2]
```
Where:
- ``AGENT_NAME`` is the name of the AI tool or framework
- ``MODEL_VERSION`` is the specific model version used
- ``[TOOL1] [TOOL2]`` are optional specialized analysis tools used
Basic development tools (git, make, editors) should not be listed.
Example:
```
Assisted-by: Claude:Opus-4.6 CodeQL
```
Maintainers reserve the right to request additional clarification or decline
contributions where LLM usage raises concerns. Ultimately, acceptance of any
contribution is at the maintainers' discretion.

1194
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,8 +22,8 @@ members = [
"devices",
"event_monitor",
"hypervisor",
"net_gen",
"net_util",
"offload_daemon",
"option_parser",
"pci",
"performance-metrics",
@@ -41,57 +41,75 @@ members = [
"vmm",
]
package.edition = "2024"
# Minimum buildable version:
# Keep in sync with version in .github/workflows/build.yaml
# Policy on MSRV (see #4318):
# Can only be bumped if satisfying any of the following:
# a.) A dependency requires it,
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
# c.) There is a security issue that is addressed by the toolchain update.
package.rust-version = "1.89.0"
resolver = "3"
[workspace.dependencies]
# rust-vmm crates
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
kvm-bindings = "0.12.1"
kvm-ioctls = "0.22.1"
linux-loader = "0.13.1"
mshv-bindings = "0.6.5"
mshv-ioctls = "0.6.5"
acpi_tables = "0.2.1"
iommufd-ioctls = "0.2.0"
kvm-bindings = "0.14.1"
kvm-ioctls = "0.25.0"
linux-loader = "0.13.2"
mshv-bindings = "0.6.9"
mshv-ioctls = "0.6.9"
seccompiler = "0.5.0"
vfio-bindings = { version = "0.6.0", default-features = false }
vfio-ioctls = { version = "0.5.1", default-features = false }
vfio_user = { version = "0.1.1", default-features = false }
vhost = { version = "0.14.0", default-features = false }
vhost-user-backend = { version = "0.20.0", default-features = false }
vfio-bindings = { version = "0.6.2", default-features = false }
vfio-ioctls = { version = "0.7.0", default-features = false }
vfio_user = { version = "0.1.3", default-features = false }
vhost = { version = "0.16.0", default-features = false }
vhost-user-backend = { version = "0.22.0", default-features = false }
virtio-bindings = "0.2.6"
virtio-queue = "0.16.0"
virtio-queue = "0.17.0"
vm-fdt = "0.3.0"
vm-memory = "0.16.1"
vmm-sys-util = "0.14.0"
vm-memory = "0.17.1"
vmm-sys-util = "0.15.0"
# igvm crates
# TODO: bump to 0.3.5 release
igvm = { git = "https://github.com/microsoft/igvm", branch = "main" }
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
igvm = "0.4.0"
igvm_defs = "0.4.0"
# serde crates
serde = "1.0.228"
serde_json = "1.0.145"
serde_with = { version = "3.16.1", default-features = false }
serde_json = "1.0.150"
serde_with = { version = "3.19.0", default-features = false }
# other crates
anyhow = "1.0.99"
bitflags = "2.10.0"
anyhow = "1.0.102"
base64 = "0.22.1"
bitflags = "2.11.1"
byteorder = "1.5.0"
cfg-if = "1.0.4"
clap = "4.5.53"
clap = "4.6.1"
dhat = "0.3.3"
dirs = "6.0.0"
env_logger = "0.11.8"
env_logger = "0.11.10"
epoll = "4.4.0"
flume = "0.12.0"
itertools = "0.14.0"
libc = "0.2.178"
log = "0.4.29"
signal-hook = "0.3.18"
thiserror = "2.0.17"
uuid = { version = "1.19.0" }
itertools = "0.15.0"
jiff = { version = "0.2", default-features = false, features = ["std"] }
libc = "0.2.186"
log = "0.4.30"
rustls = { version = "0.23.40", default-features = false, features = [
"logging",
"ring",
"std",
] }
sha2 = "0.11.0"
signal-hook = "0.4.4"
signal-hook-registry = "1.4.8"
smallvec = "1.15.1"
thiserror = "2.0.18"
uuid = { version = "1.23.2" }
wait-timeout = "0.2.1"
zerocopy = { version = "0.8.31", default-features = false }
zerocopy = { version = "0.8.50", default-features = false }
[workspace.lints.clippy]
# Any clippy lint (group) in alphabetical order:
@@ -106,6 +124,7 @@ style = "deny"
suspicious = "deny"
# Individual Lints
absolute_paths = "deny"
assertions_on_result_states = "deny"
if_not_else = "deny"
manual_string_new = "deny"

View File

@@ -59,10 +59,10 @@ based on the [Rust VMM](https://github.com/rust-vmm) crates.
### Architectures
Cloud Hypervisor supports the `x86-64`, `AArch64` and `riscv64`
architectures, with functionality varying across these platforms. The
functionality differences between `x86-64` and `AArch64` are documented
in [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125).
Cloud Hypervisor's main supported architectures are `x86-64` and `AArch64`,
with functionality varying across these platforms. The functionality
differences between `x86-64` and `AArch64` are documented in
[#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125).
The `riscv64` architecture support is experimental and offers limited
functionality. For more details and instructions, please refer to [riscv
documentation](docs/riscv.md).
@@ -111,19 +111,25 @@ do not wish to use the pre-built binaries.
## Booting Linux
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
built with PVH support or a bzImage) or booting via a firmware (either [Rust Hypervisor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
Cloud Hypervisor boots guests in one of two ways. The first is direct
kernel boot, where a kernel image is passed to `--kernel`. The x86-64
kernel must be built with PVH support or be a bzImage. The second is
firmware boot, where a firmware image is passed to `--firmware` and
brings up the guest's normal boot loader.
Binary builds of the firmware files are available for the latest release of
[Rust Hypervisor
Two firmware options are supported, and which one works best depends
on the guest OS. [Rust Hypervisor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware)
is a lightweight Rust-based PVH firmware. The edk2 UEFI firmware is
called `CLOUDHV.fd` for x86-64 and `CLOUDHV_EFI.fd` for AArch64.
Prebuilt binaries for both are available at their respective releases
pages, [Rust Hypervisor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
and [our edk2
repository](https://github.com/cloud-hypervisor/edk2/releases/latest)
The choice of firmware depends on your guest OS choice; some experimentation
may be required.
fork](https://github.com/cloud-hypervisor/edk2/releases/latest).
The edk2 fork carries customizations required to boot AArch64 guests
on cloud-hypervisor. See [docs/uefi.md](docs/uefi.md) for differences
with upstream tianocore/edk2.
### Firmware Booting
@@ -197,7 +203,7 @@ To build the kernel:
```shell
# Clone the Cloud Hypervisor Linux branch
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8 linux-cloud-hypervisor
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.16.9 linux-cloud-hypervisor
$ pushd linux-cloud-hypervisor
$ make ch_defconfig
# Do native build of the x86-64 kernel

View File

@@ -1,7 +1,9 @@
[package]
authors = ["The Cloud Hypervisor Authors"]
edition.workspace = true
license = "Apache-2.0"
name = "api_client"
rust-version.workspace = true
version = "0.1.0"
[dependencies]

View File

@@ -3,24 +3,26 @@
// SPDX-License-Identifier: Apache-2.0
//
use std::io::{Read, Write};
use std::io::{self, Read, Write};
use std::os::unix::io::RawFd;
use std::{num, str};
use thiserror::Error;
use vmm_sys_util::errno;
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
#[derive(Debug, Error)]
pub enum Error {
#[error("Error writing to or reading from HTTP socket")]
Socket(#[source] std::io::Error),
Socket(#[source] io::Error),
#[error("Error sending file descriptors")]
SocketSendFds(#[source] vmm_sys_util::errno::Error),
SocketSendFds(#[source] errno::Error),
#[error("Error parsing HTTP status code")]
StatusCodeParsing(#[source] std::num::ParseIntError),
StatusCodeParsing(#[source] num::ParseIntError),
#[error("HTTP output is missing protocol statement")]
MissingProtocol,
#[error("Error parsing HTTP Content-Length field")]
ContentLengthParsing(#[source] std::num::ParseIntError),
ContentLengthParsing(#[source] num::ParseIntError),
#[error("Server responded with error {0:?}: {1:?}")]
ServerResponse(
StatusCode,
@@ -100,7 +102,7 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
if count == 0 {
break;
}
res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
res.push_str(str::from_utf8(&bytes[0..count]).unwrap());
// End of headers
if let Some(o) = res.find("\r\n\r\n") {

View File

@@ -2,6 +2,7 @@
authors = ["The Chromium OS Authors"]
edition.workspace = true
name = "arch"
rust-version.workspace = true
version = "0.1.0"
[features]
@@ -24,6 +25,10 @@ uuid = { workspace = true }
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
vmm-sys-util = { workspace = true, features = ["with-serde"] }
[dev-dependencies]
proptest = "1.0.0"
serde_json = { workspace = true }
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
fdt_parser = { version = "0.1.5", package = "fdt" }
vm-fdt = { workspace = true }

188
arch/src/aarch64/cache.rs Normal file
View File

@@ -0,0 +1,188 @@
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
use std::fs;
use std::path::Path;
use log::warn;
#[derive(Copy, Clone)]
pub enum CacheLevel {
/// L1 data cache
L1D = 0,
/// L1 instruction cache
L1I = 1,
/// L2 cache
L2 = 2,
/// L3 cache
L3 = 3,
}
/// NOTE: cache size file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/size".
pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
match cache_level {
CacheLevel::L1D => file_directory += "/index0/size",
CacheLevel::L1I => file_directory += "/index1/size",
CacheLevel::L2 => file_directory += "/index2/size",
CacheLevel::L3 => file_directory += "/index3/size",
}
let file_path = Path::new(&file_directory);
if file_path.exists() {
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
// The content of the file is as simple as a size, like: "32K"
let src = src.trim();
let src_digits: u32 = src[0..src.len() - 1].parse().unwrap();
let src_unit = &src[src.len() - 1..];
src_digits
* match src_unit {
"K" => 1u32 << 10,
"M" => 1u32 << 20,
"G" => 1u32 << 30,
_ => 1,
}
} else {
0
}
}
/// NOTE: coherency_line_size file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size".
pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
match cache_level {
CacheLevel::L1D => file_directory += "/index0/coherency_line_size",
CacheLevel::L1I => file_directory += "/index1/coherency_line_size",
CacheLevel::L2 => file_directory += "/index2/coherency_line_size",
CacheLevel::L3 => file_directory += "/index3/coherency_line_size",
}
let file_path = Path::new(&file_directory);
if file_path.exists() {
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
src.trim().parse::<u32>().unwrap()
} else {
0
}
}
/// NOTE: number_of_sets file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/number_of_sets".
pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
match cache_level {
CacheLevel::L1D => file_directory += "/index0/number_of_sets",
CacheLevel::L1I => file_directory += "/index1/number_of_sets",
CacheLevel::L2 => file_directory += "/index2/number_of_sets",
CacheLevel::L3 => file_directory += "/index3/number_of_sets",
}
let file_path = Path::new(&file_directory);
if file_path.exists() {
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
src.trim().parse::<u32>().unwrap()
} else {
0
}
}
/// NOTE: shared_cpu_list file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_list".
pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
let mut result = true;
match cache_level {
CacheLevel::L1D | CacheLevel::L1I => result = false,
CacheLevel::L2 => file_directory += "/index2/shared_cpu_list",
CacheLevel::L3 => file_directory += "/index3/shared_cpu_list",
}
if !result {
return false;
}
let file_path = Path::new(&file_directory);
if file_path.exists() {
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
let src = src.trim();
if src.is_empty() {
result = false;
} else {
result = src.contains('-') || src.contains(',');
}
} else {
result = false;
}
result
}
#[derive(Default, Copy, Clone, Debug)]
pub struct CacheTopologyInfo {
pub l1_d_cache_size: u32,
pub l1_d_cache_line_size: u32,
pub l1_d_cache_sets: u32,
pub l1_i_cache_size: u32,
pub l1_i_cache_line_size: u32,
pub l1_i_cache_sets: u32,
pub l2_cache_size: u32,
pub l2_cache_line_size: u32,
pub l2_cache_sets: u32,
pub l3_cache_size: u32,
pub l3_cache_line_size: u32,
pub l3_cache_sets: u32,
pub l2_cache_shared: bool,
pub l3_cache_shared: bool,
}
/// Reads cache topology information from sysfs for cpu0.
pub fn read_cache_topology() -> Option<CacheTopologyInfo> {
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
if !cache_path.exists() {
warn!("Cache topology information is not available in sysfs.");
return None;
}
let mut info = CacheTopologyInfo {
l1_d_cache_size: get_cache_size(CacheLevel::L1D),
l1_d_cache_line_size: get_cache_coherency_line_size(CacheLevel::L1D),
l1_d_cache_sets: get_cache_number_of_sets(CacheLevel::L1D),
l1_i_cache_size: get_cache_size(CacheLevel::L1I),
l1_i_cache_line_size: get_cache_coherency_line_size(CacheLevel::L1I),
l1_i_cache_sets: get_cache_number_of_sets(CacheLevel::L1I),
l2_cache_size: get_cache_size(CacheLevel::L2),
l2_cache_line_size: get_cache_coherency_line_size(CacheLevel::L2),
l2_cache_sets: get_cache_number_of_sets(CacheLevel::L2),
l3_cache_size: get_cache_size(CacheLevel::L3),
l3_cache_line_size: get_cache_coherency_line_size(CacheLevel::L3),
l3_cache_sets: get_cache_number_of_sets(CacheLevel::L3),
l2_cache_shared: false,
l3_cache_shared: false,
};
if info.l2_cache_size != 0 {
info.l2_cache_shared = get_cache_shared(CacheLevel::L2);
}
if info.l3_cache_size != 0 {
info.l3_cache_shared = get_cache_shared(CacheLevel::L3);
}
Some(info)
}

View File

@@ -9,22 +9,24 @@
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt::Debug;
use std::path::Path;
use std::hash::BuildHasher;
use std::sync::{Arc, Mutex};
use std::{cmp, fs, result, str};
use std::{cmp, result, str};
use byteorder::{BigEndian, ByteOrder};
use fdt_parser::node::FdtNode;
use hypervisor::arch::aarch64::gic::Vgic;
use hypervisor::arch::aarch64::regs::{
AARCH64_ARCH_TIMER_HYP_IRQ, AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ, AARCH64_ARCH_TIMER_VIRT_IRQ, AARCH64_PMU_IRQ,
};
use log::{debug, warn};
use log::{debug, info};
use thiserror::Error;
use vm_fdt::{FdtWriter, FdtWriterResult};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
use super::cache::{CacheTopologyInfo, read_cache_topology};
use super::layout::{
GIC_V2M_COMPATIBLE, GICV2M_SPI_BASE, GICV2M_SPI_NUM, IRQ_BASE, MEM_32BIT_DEVICES_SIZE,
MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START, PCI_HIGH_BASE,
@@ -88,124 +90,9 @@ pub enum Error {
}
type Result<T> = result::Result<T, Error>;
#[derive(Copy, Clone)]
pub enum CacheLevel {
/// L1 data cache
L1D = 0,
/// L1 instruction cache
L1I = 1,
/// L2 cache
L2 = 2,
/// L3 cache
L3 = 3,
}
/// NOTE: cache size file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/size".
pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
match cache_level {
CacheLevel::L1D => file_directory += "/index0/size",
CacheLevel::L1I => file_directory += "/index1/size",
CacheLevel::L2 => file_directory += "/index2/size",
CacheLevel::L3 => file_directory += "/index3/size",
}
let file_path = Path::new(&file_directory);
if file_path.exists() {
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
// The content of the file is as simple as a size, like: "32K"
let src = src.trim();
let src_digits: u32 = src[0..src.len() - 1].parse().unwrap();
let src_unit = &src[src.len() - 1..];
src_digits
* match src_unit {
"K" => 1024,
"M" => 1024u32.pow(2),
"G" => 1024u32.pow(3),
_ => 1,
}
} else {
0
}
}
/// NOTE: coherency_line_size file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size".
pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
match cache_level {
CacheLevel::L1D => file_directory += "/index0/coherency_line_size",
CacheLevel::L1I => file_directory += "/index1/coherency_line_size",
CacheLevel::L2 => file_directory += "/index2/coherency_line_size",
CacheLevel::L3 => file_directory += "/index3/coherency_line_size",
}
let file_path = Path::new(&file_directory);
if file_path.exists() {
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
src.trim().parse::<u32>().unwrap()
} else {
0
}
}
/// NOTE: number_of_sets file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/number_of_sets".
pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
match cache_level {
CacheLevel::L1D => file_directory += "/index0/number_of_sets",
CacheLevel::L1I => file_directory += "/index1/number_of_sets",
CacheLevel::L2 => file_directory += "/index2/number_of_sets",
CacheLevel::L3 => file_directory += "/index3/number_of_sets",
}
let file_path = Path::new(&file_directory);
if file_path.exists() {
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
src.trim().parse::<u32>().unwrap()
} else {
0
}
}
/// NOTE: shared_cpu_list file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_list".
pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
let mut result = true;
match cache_level {
CacheLevel::L1D | CacheLevel::L1I => result = false,
CacheLevel::L2 => file_directory += "/index2/shared_cpu_list",
CacheLevel::L3 => file_directory += "/index3/shared_cpu_list",
}
if !result {
return false;
}
let file_path = Path::new(&file_directory);
if file_path.exists() {
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
let src = src.trim();
if src.is_empty() {
result = false;
} else {
result = src.contains('-') || src.contains(',');
}
} else {
result = false;
}
result
}
/// Creates the flattened device tree for this aarch64 VM.
#[allow(clippy::too_many_arguments)]
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
#[expect(clippy::too_many_arguments)]
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: BuildHasher>(
guest_mem: &GuestMemoryMmap,
cmdline: &str,
vcpu_mpidr: &[u64],
@@ -287,62 +174,34 @@ fn create_cpu_nodes(
threads_per_core as u32 * cores_per_die as u32 * dies_per_package as u32 * packages as u32;
// Add cache info.
// L1 Data Cache Info.
let mut l1_d_cache_size: u32 = 0;
let mut l1_d_cache_line_size: u32 = 0;
let mut l1_d_cache_sets: u32 = 0;
let cache_info = read_cache_topology();
let cache_exist = cache_info.is_some();
let CacheTopologyInfo {
l1_d_cache_size,
l1_d_cache_line_size,
l1_d_cache_sets,
l1_i_cache_size,
l1_i_cache_line_size,
l1_i_cache_sets,
l2_cache_size,
l2_cache_line_size,
l2_cache_sets,
l3_cache_size,
l3_cache_line_size,
l3_cache_sets,
l2_cache_shared,
l3_cache_shared,
} = cache_info.unwrap_or_default();
// L1 Instruction Cache Info.
let mut l1_i_cache_size: u32 = 0;
let mut l1_i_cache_line_size: u32 = 0;
let mut l1_i_cache_sets: u32 = 0;
// L2 Cache Info.
let mut l2_cache_size: u32 = 0;
let mut l2_cache_line_size: u32 = 0;
let mut l2_cache_sets: u32 = 0;
// L3 Cache Info.
let mut l3_cache_size: u32 = 0;
let mut l3_cache_line_size: u32 = 0;
let mut l3_cache_sets: u32 = 0;
// Cache Shared Info.
let mut l2_cache_shared: bool = false;
let mut l3_cache_shared: bool = false;
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
let cache_exist: bool = cache_path.exists();
if cache_exist {
// L1 Data Cache Info.
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
l1_d_cache_sets = get_cache_number_of_sets(CacheLevel::L1D);
// L1 Instruction Cache Info.
l1_i_cache_size = get_cache_size(CacheLevel::L1I);
l1_i_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1I);
l1_i_cache_sets = get_cache_number_of_sets(CacheLevel::L1I);
// L2 Cache Info.
l2_cache_size = get_cache_size(CacheLevel::L2);
l2_cache_line_size = get_cache_coherency_line_size(CacheLevel::L2);
l2_cache_sets = get_cache_number_of_sets(CacheLevel::L2);
// L3 Cache Info.
l3_cache_size = get_cache_size(CacheLevel::L3);
l3_cache_line_size = get_cache_coherency_line_size(CacheLevel::L3);
l3_cache_sets = get_cache_number_of_sets(CacheLevel::L3);
// Cache Shared Info.
if l2_cache_size != 0 {
l2_cache_shared = get_cache_shared(CacheLevel::L2);
}
if l3_cache_size != 0 {
l3_cache_shared = get_cache_shared(CacheLevel::L3);
}
} else {
warn!("cache sysfs system does not exist.");
// Arm boot protocol requires a minimal Device Tree
// https://docs.kernel.org/arch/arm64/booting.html
// As Generic initiators are supported only in ACPI
// When a guest kernel does not boot under "acpi=force" mode it can
// hang due to conflicting numa information present in FDT which
// does not support Generic Initiators
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
if has_generic_initiator {
info!("Skipping NUMA CPU node encoding in FDT with Generic Initiator devices");
}
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
@@ -359,8 +218,10 @@ fn create_cpu_nodes(
fdt.property_u32("reg", (mpidr & 0x7FFFFF) as u32)?;
fdt.property_u32("phandle", cpu_id as u32 + FIRST_VCPU_PHANDLE)?;
// Add `numa-node-id` property if there is any numa config.
if numa_nodes.len() > 1 {
// Skipping NUMA encoding in FDT when Generic Initiator devices
// are present allowed such guest kernels to boot properly and
// rely solely on ACPI tables to setup NUMA
if numa_nodes.len() > 1 && !has_generic_initiator {
for numa_node_idx in 0..numa_nodes.len() {
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
if numa_node.unwrap().cpus.contains(&(cpu_id as u32)) {
@@ -501,7 +362,14 @@ fn create_memory_node(
) -> FdtWriterResult<()> {
// See https://github.com/torvalds/linux/blob/58ae0b51506802713aa0e9956d1853ba4c722c98/Documentation/devicetree/bindings/numa.txt
// for NUMA setting in memory node.
if numa_nodes.len() > 1 {
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
if has_generic_initiator {
info!("Skipping NUMA memory node encoding in FDT with Generic Initiator devices");
}
// Skipping NUMA encoding in FDT when Generic Initiator devices
// are present allowed guest kernels to boot and
// rely solely on ACPI tables to setup NUMA
if numa_nodes.len() > 1 && !has_generic_initiator {
for numa_node_idx in 0..numa_nodes.len() {
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
let mut mem_reg_prop: Vec<u64> = Vec::new();
@@ -518,12 +386,15 @@ fn create_memory_node(
node_memory_addr = memory_region_start_addr;
}
}
let memory_node_name = format!("memory@{node_memory_addr:x}");
let memory_node = fdt.begin_node(&memory_node_name)?;
fdt.property_string("device_type", "memory")?;
fdt.property_array_u64("reg", &mem_reg_prop)?;
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
fdt.end_node(memory_node)?;
// Only create a memory node if this NUMA node has memory regions
if !mem_reg_prop.is_empty() {
let memory_node_name = format!("memory@{node_memory_addr:x}");
let memory_node = fdt.begin_node(&memory_node_name)?;
fdt.property_string("device_type", "memory")?;
fdt.property_array_u64("reg", &mem_reg_prop)?;
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
fdt.end_node(memory_node)?;
}
}
} else {
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
@@ -856,7 +727,7 @@ fn create_fw_cfg_node<T: DeviceInfoForFdt + Clone + Debug>(
Ok(())
}
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: BuildHasher>(
fdt: &mut FdtWriter,
dev_info: &HashMap<(DeviceType, String), T, S>,
) -> FdtWriterResult<()> {
@@ -1044,6 +915,22 @@ fn create_pci_nodes(
}
fn create_distance_map_node(fdt: &mut FdtWriter, numa_nodes: &NumaNodes) -> FdtWriterResult<()> {
// When Generic Initiator nodes are present, skip ALL FDT NUMA information.
// Let ACPI (which supports Generic Initiator via SRAT Type 5) handle the entire NUMA topology.
// FDT cannot represent Generic Initiator nodes, and mixing FDT + ACPI NUMA info causes conflicts.
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
if has_generic_initiator {
info!("Skipping NUMA distance map encoding in FDT with Generic Initiator devices");
return Ok(());
}
// At this point, we know there are no Generic Initiator nodes
let mut numa_ids: Vec<u32> = numa_nodes.keys().cloned().collect();
// If we only have one node, no distance map is needed
if numa_ids.len() <= 1 {
return Ok(());
}
let distance_map_node = fdt.begin_node("distance-map")?;
fdt.property_string("compatible", "numa-distance-map-v1")?;
// Construct the distance matrix.
@@ -1056,26 +943,33 @@ fn create_distance_map_node(fdt: &mut FdtWriter, numa_nodes: &NumaNodes) -> FdtW
// a value greater than 10.
// 4. distance-matrix should have entries in lexicographical ascending
// order of nodes.
numa_ids.sort_unstable(); // lexicographical order
let mut distance_matrix = Vec::new();
for numa_node_idx in 0..numa_nodes.len() {
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
for dest_numa_node in 0..numa_node.unwrap().distances.len() + 1 {
if numa_node_idx == dest_numa_node {
distance_matrix.push(numa_node_idx as u32);
distance_matrix.push(dest_numa_node as u32);
// Iterate over actual numa IDs instead of 0..len()
for numa_id in numa_ids.iter() {
let numa_node = &numa_nodes[numa_id];
for dest_numa_id in numa_ids.iter() {
if *numa_id == *dest_numa_id {
distance_matrix.push(*numa_id);
distance_matrix.push(*dest_numa_id);
distance_matrix.push(10_u32);
continue;
}
distance_matrix.push(numa_node_idx as u32);
distance_matrix.push(dest_numa_node as u32);
distance_matrix.push(
*numa_node
.unwrap()
.distances
.get(&(dest_numa_node as u32))
.unwrap() as u32,
);
distance_matrix.push(*numa_id);
distance_matrix.push(*dest_numa_id);
// Use user-specified distance, checking both directions for symmetry
let distance = if let Some(&dist) = numa_node.distances.get(dest_numa_id) {
// Forward direction: current node -> dest node
dist
} else if let Some(dest_node) = numa_nodes.get(dest_numa_id) {
// Reverse direction for symmetry: dest node -> current node
dest_node.distances.get(numa_id).copied().unwrap_or(20)
} else {
// Default distance when neither direction is specified
20
};
distance_matrix.push(distance as u32);
}
}
fdt.property_array_u32("distance-matrix", distance_matrix.as_ref())?;
@@ -1099,7 +993,7 @@ pub fn print_fdt(dtb: &[u8]) {
}
}
fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
fn print_node(node: FdtNode<'_, '_>, n_spaces: usize) {
debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
for property in node.properties() {
let name = property.name;
@@ -1160,3 +1054,118 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
print_node(child, n_spaces + 2);
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::*;
use crate::NumaNode;
// Helper function to create a simple NumaNode for testing
fn create_test_numa_node(cpus: Vec<u32>, device_id: Option<String>) -> NumaNode {
NumaNode {
memory_regions: Vec::new(),
hotplug_regions: Vec::new(),
cpus,
pci_segments: Vec::new(),
distances: BTreeMap::new(),
memory_zones: Vec::new(),
device_id,
}
}
#[test]
fn test_fdt_generic_initiator_detection_and_skip() {
// No Generic Initiator - should not skip FDT NUMA
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
numa_nodes.insert(1, create_test_numa_node(vec![2, 3], None));
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
assert!(
!has_gi,
"Should not detect Generic Initiator when none present"
);
// One Generic Initiator - should skip FDT NUMA
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
numa_nodes.insert(1, create_test_numa_node(vec![], Some("vfio0".to_string())));
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
assert!(has_gi, "Should detect Generic Initiator when present");
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(result.is_ok(), "Should skip distance map when GI present");
// Multiple Generic Initiators - should skip FDT NUMA
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
numa_nodes.insert(1, create_test_numa_node(vec![], Some("vfio0".to_string())));
numa_nodes.insert(2, create_test_numa_node(vec![], Some("vfio1".to_string())));
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
assert!(has_gi, "Should detect multiple Generic Initiators");
}
#[test]
fn test_fdt_distance_map() {
// Single NUMA node - should skip distance map
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(result.is_ok(), "Should skip distance map for single node");
// Empty NUMA nodes - should handle gracefully
let numa_nodes = BTreeMap::new();
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(result.is_ok(), "Should handle empty NUMA nodes");
// Non-contiguous NUMA IDs (0, 2, 5) with distance symmetry
let mut numa_nodes = BTreeMap::new();
let mut node0 = create_test_numa_node(vec![0], None);
node0.distances.insert(2, 20);
// node0 has no explicit distance to node5
let mut node2 = create_test_numa_node(vec![1], None);
node2.distances.insert(0, 20);
node2.distances.insert(5, 25);
let mut node5 = create_test_numa_node(vec![2], None);
node5.distances.insert(0, 30);
node5.distances.insert(2, 25);
// node5->node0 (should be used for node0->node5)
numa_nodes.insert(0, node0);
numa_nodes.insert(2, node2);
numa_nodes.insert(5, node5);
// Verify IDs are sorted lexicographically
let mut numa_ids: Vec<u32> = numa_nodes.keys().cloned().collect();
numa_ids.sort_unstable();
assert_eq!(numa_ids, vec![0, 2, 5]);
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(
result.is_ok(),
"Should handle non-contiguous IDs and symmetry"
);
// Default distance (20) when no distance specified in either direction
let mut numa_nodes = BTreeMap::new();
numa_nodes.insert(0, create_test_numa_node(vec![0], None));
numa_nodes.insert(1, create_test_numa_node(vec![1], None));
// Neither node has distance to the other
let mut fdt = FdtWriter::new().unwrap();
let result = create_distance_map_node(&mut fdt, &numa_nodes);
assert!(result.is_ok(), "Should default to 20 for missing distances");
}
}

View File

@@ -2,6 +2,8 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
/// Module for cache info.
pub mod cache;
/// Module for the flattened device tree.
pub mod fdt;
/// Layout for this aarch64 system.
@@ -11,6 +13,7 @@ pub mod uefi;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::BuildHasher;
use std::sync::{Arc, Mutex};
use hypervisor::arch::aarch64::gic::Vgic;
@@ -121,8 +124,8 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
}
/// Configures the system and should be called once per vm before starting vcpu threads.
#[allow(clippy::too_many_arguments)]
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
#[expect(clippy::too_many_arguments)]
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: BuildHasher>(
guest_mem: &GuestMemoryMmap,
cmdline: &str,
vcpu_mpidr: &[u64],

View File

@@ -7,7 +7,7 @@ use std::os::fd::AsFd;
use std::result;
use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
use vm_memory::{Bytes, GuestAddress, GuestMemory};
/// Errors thrown while loading UEFI binary
#[derive(Debug, Error)]

View File

@@ -9,14 +9,17 @@
//! Supported platforms: x86_64, aarch64, riscv64.
use std::collections::BTreeMap;
use std::str::FromStr;
use std::sync::Arc;
use std::{fmt, result};
use serde::de::{IntoDeserializer, value};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use vm_memory::bitmap::AtomicBitmap;
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
type GuestRegionMmap = vm_memory::GuestRegionMmap<AtomicBitmap>;
/// Type for returning error code.
#[derive(Debug, Error)]
@@ -33,11 +36,13 @@ pub enum Error {
#[error("The memory map table extends past the end of guest memory")]
MemmapTablePastRamEnd,
#[error("Error writing memory map table to guest memory")]
MemmapTableSetup,
MemmapTableSetup(#[source] vm_memory::GuestMemoryError),
#[error("Error generating memory map table")]
MemmapTableGeneration,
#[error("The hvm_start_info structure extends past the end of guest memory")]
StartInfoPastRamEnd,
#[error("Error writing hvm_start_info to guest memory")]
StartInfoSetup,
StartInfoSetup(#[source] vm_memory::GuestMemoryError),
#[error("Failed to compute initramfs address")]
InitramfsAddress,
#[error("Error writing module entry to guest memory")]
@@ -53,6 +58,26 @@ pub enum Error {
/// Type for returning public functions outcome.
pub type Result<T> = result::Result<T, Error>;
// If the target_arch is x86_64 we import CpuProfile from the x86_64 module, otherwise we
// declare it here with only "host" as a selectable CPU profile. This trick is useful to prevent
// excessive conditional compilation throughout the codebase.
#[cfg(not(target_arch = "x86_64"))]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
/// A [`CpuProfile`] is a mechanism for ensuring live migration compatibility
/// between host's with potentially different CPU models.
pub enum CpuProfile {
#[default]
Host,
}
// Note that this trait impl is architecture agnostic and may thus reside here.
impl FromStr for CpuProfile {
type Err = value::Error;
fn from_str(s: &str) -> result::Result<Self, Self::Err> {
Self::deserialize(s.into_deserializer())
}
}
/// Type for memory region types.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum RegionType {
@@ -100,8 +125,9 @@ pub mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use x86_64::{
_NSIG, CpuidConfig, CpuidFeatureEntry, EntryPoint, arch_memory_regions, configure_system,
configure_vcpu, generate_common_cpuid, generate_ram_ranges, get_host_cpu_phys_bits,
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs,
configure_vcpu, cpu_profile::CpuProfile, generate_common_cpuid, generate_ram_ranges,
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
layout::CMDLINE_START, regs,
};
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
@@ -120,6 +146,7 @@ pub struct NumaNode {
pub pci_segments: Vec<u16>,
pub distances: BTreeMap<u32, u8>,
pub memory_zones: Vec<String>,
pub device_id: Option<String>,
}
pub type NumaNodes = BTreeMap<u32, NumaNode>;

View File

@@ -61,7 +61,7 @@ pub enum Error {
type Result<T> = result::Result<T, Error>;
/// Creates the flattened device tree for this riscv64 VM.
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
guest_mem: &GuestMemoryMmap,
cmdline: &str,
@@ -71,6 +71,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
aia_device: &Arc<Mutex<dyn Vaia>>,
initrd: &Option<InitramfsConfig>,
pci_space_info: &[PciSpaceInfo],
timebase_frequency: u32,
) -> FdtWriterResult<Vec<u8>> {
// Allocate stuff necessary for the holding the blob.
let mut fdt = FdtWriter::new()?;
@@ -86,7 +87,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
// Properties
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
fdt.property_u32("#size-cells", SIZE_CELLS)?;
create_cpu_nodes(&mut fdt, num_vcpu, isa_string)?;
create_cpu_nodes(&mut fdt, num_vcpu, isa_string, timebase_frequency)?;
create_memory_node(&mut fdt, guest_mem)?;
create_chosen_node(&mut fdt, cmdline, initrd)?;
create_aia_node(&mut fdt, aia_device)?;
@@ -110,14 +111,17 @@ pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Res
}
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32, isa_string: &str) -> FdtWriterResult<()> {
fn create_cpu_nodes(
fdt: &mut FdtWriter,
num_cpus: u32,
isa_string: &str,
timebase_frequency: u32,
) -> FdtWriterResult<()> {
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
let cpus = fdt.begin_node("cpus")?;
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
fdt.property_u32("#address-cells", 0x01)?;
fdt.property_u32("#size-cells", 0x0)?;
// TODO: Retrieve CPU frequency from cpu timer regs
let timebase_frequency: u32 = 0x989680;
fdt.property_u32("timebase-frequency", timebase_frequency)?;
for cpu_index in 0..num_cpus {
@@ -231,8 +235,8 @@ fn create_aia_node(fdt: &mut FdtWriter, aia_device: &Arc<Mutex<dyn Vaia>>) -> Fd
fdt.property_u32("#interrupt-cells", 0u32)?;
fdt.property_null("interrupt-controller")?;
fdt.property_null("msi-controller")?;
// TODO complete num-ids
fdt.property_u32("riscv,num-ids", 2047u32)?;
let imsic_num_ids = aia_device.lock().unwrap().imsic_num_ids();
fdt.property_u32("riscv,num-ids", imsic_num_ids)?;
fdt.property_u32("phandle", AIA_IMSIC_PHANDLE)?;
let mut irq_cells = Vec::new();

View File

@@ -160,7 +160,6 @@ fn isa_string_from_host() -> Result<String, Error> {
}
/// Configures the system and should be called once per vm before starting vcpu threads.
#[allow(clippy::too_many_arguments)]
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
guest_mem: &GuestMemoryMmap,
cmdline: &str,
@@ -169,6 +168,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
initrd: &Option<super::InitramfsConfig>,
pci_space_info: &[PciSpaceInfo],
aia_device: &Arc<Mutex<dyn Vaia>>,
timebase_frequency: u32,
) -> super::Result<()> {
let isa_string = isa_string_from_host()?;
let fdt_final = fdt::create_fdt(
@@ -180,6 +180,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
aia_device,
initrd,
pci_space_info,
timebase_frequency,
)
.map_err(|_| Error::SetupFdt)?;

View File

@@ -7,7 +7,7 @@ use std::os::fd::AsFd;
use std::result;
use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
use vm_memory::{Bytes, GuestAddress, GuestMemory};
/// Errors thrown while loading UEFI binary
#[derive(Debug, Error)]

View File

@@ -0,0 +1,190 @@
// Copyright © 2026 Cyberus Technology GmbH
//
// SPDX-License-Identifier: Apache-2.0
//
//! This module contains types associated with adjusting CPUID entries according
//! to a selected CPU profile.
use std::ops::RangeInclusive;
use hypervisor::arch::x86::CpuIdEntry;
use log::error;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::x86_64::{CpuidReg, deserialize_u32_hex, serialize_u32_hex};
/// Parameters for inspecting CPUID definitions.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct CpuidParameters {
/// The leaf (EAX) parameter used with the CPUID instruction
#[serde(
serialize_with = "serialize_u32_hex",
deserialize_with = "deserialize_u32_hex"
)]
pub leaf: u32,
/// The sub-leaf (ECX) parameter used with the CPUID instruction
#[serde(
serialize_with = "serialize_range_hex",
deserialize_with = "deserialize_range_hex"
)]
pub sub_leaf: RangeInclusive<u32>,
/// The register we are interested in inspecting which gets filled by the CPUID instruction
pub register: CpuidReg,
}
// Only used for (de-)serialization
#[derive(Debug, Serialize, Deserialize)]
struct ProvisionalRangeInclusive {
#[serde(
serialize_with = "serialize_u32_hex",
deserialize_with = "deserialize_u32_hex"
)]
start: u32,
#[serde(
serialize_with = "serialize_u32_hex",
deserialize_with = "deserialize_u32_hex"
)]
end: u32,
}
fn serialize_range_hex<S: serde::Serializer>(
input: &RangeInclusive<u32>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let provisional = ProvisionalRangeInclusive {
start: *input.start(),
end: *input.end(),
};
provisional.serialize(serializer)
}
fn deserialize_range_hex<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<RangeInclusive<u32>, D::Error> {
let ProvisionalRangeInclusive { start, end } =
ProvisionalRangeInclusive::deserialize(deserializer)?;
Ok(start..=end)
}
/// Used for adjusting an entire cpuid output register (EAX, EBX, ECX or EDX).
///
/// Instances of this struct typically adjust CPUID according to the following
/// formula: `cpuid_reg_value = (self.mask & cpuid_reg_value) | self.replacements`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct CpuidOutputRegisterAdjustments {
/// Packs values to be placed into the given CPUID output register.
#[serde(
serialize_with = "serialize_u32_hex",
deserialize_with = "deserialize_u32_hex"
)]
pub replacements: u32,
/// Used to zero out the area `replacements` occupy. This mask is not necessarily !replacements, as replacements
/// may pack values of different types that occupy varying ranges of bits.
///
/// Bit ranges within a CPUID output register that are **not** supposed to be replaced/overwritten should be set in
/// this mask.
#[serde(
serialize_with = "serialize_u32_hex",
deserialize_with = "deserialize_u32_hex"
)]
pub mask: u32,
}
/// Error type indicating that expected CPUID entries could not be found.
///
/// This type does not record which entries could not be found as we do not
/// expect this to be actionable at runtime. Instead we encourage logging such
/// violations when and where they are detected.
#[derive(Debug, Error)]
#[error("Required CPUID entries not found")]
pub struct MissingCpuidEntriesError;
impl CpuidOutputRegisterAdjustments {
/// Adjust the given `cpuid_output_register` by retaining and replacing values according to `self`.
fn adjust(self, cpuid_output_register: &mut u32) {
*cpuid_output_register &= self.mask;
*cpuid_output_register |= self.replacements;
}
/// Adjust `cpuid` according to the given `adjustments`.
///
/// The returned vector of cpuid entries covers the same CPUID (sub-) leaves as the given `cpuid` input,
/// but values without matching [`CpuidParameters`] are zeroed out.
///
/// # Errors
///
/// An error is returned if an entry cannot be found for an adjustment describing non-zero replacements.
pub(super) fn adjust_cpuid_entries(
mut cpuid: Vec<CpuIdEntry>,
adjustments: &[(CpuidParameters, Self)],
) -> Result<Vec<CpuIdEntry>, MissingCpuidEntriesError> {
for entry in &mut cpuid {
for (reg, reg_value) in [
(CpuidReg::EAX, &mut entry.eax),
(CpuidReg::EBX, &mut entry.ebx),
(CpuidReg::ECX, &mut entry.ecx),
(CpuidReg::EDX, &mut entry.edx),
] {
// Lookup the adjustment corresponding to the entry's function/leaf and index/sub-leaf for each of the register.
let register_adjustments: Option<CpuidOutputRegisterAdjustments> =
adjustments.iter().find_map(|(param, adjustment)| {
((param.leaf == entry.function)
&& param.sub_leaf.contains(&entry.index)
&& (param.register == reg))
.then_some(*adjustment)
});
match register_adjustments {
Some(adjustment) => adjustment.adjust(reg_value),
None => {
// No matching cpuid parameters were found. We thus set the value of the register to 0.
*reg_value = 0;
}
}
}
}
Self::expected_entries_found(&cpuid, adjustments)?;
Ok(cpuid)
}
/// Check that we found every value that was supposed to be replaced with something else than 0
///
/// IMPORTANT: This function assumes that the given `cpuid` has already been adjusted with the
/// provided `adjustments`.
fn expected_entries_found(
cpuid: &[CpuIdEntry],
adjustments: &[(CpuidParameters, Self)],
) -> Result<(), MissingCpuidEntriesError> {
let mut missing_entry = false;
for (param, adjustment) in adjustments {
if adjustment.replacements == 0 {
continue;
}
if !cpuid.iter().any(|entry| {
(entry.function == param.leaf) && (param.sub_leaf.contains(&entry.index))
}) {
error!(
"cannot adjust CPU profile. No entry found matching the required parameters: {param:?}"
);
missing_entry = true;
}
}
if missing_entry {
Err(MissingCpuidEntriesError)
} else {
Ok(())
}
}
}
/// Data describing CPUID adjustments related to a CPU Profile.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuidProfileData {
/// Adjustments necessary to become compatible with the desired target.
pub adjustments: Vec<(CpuidParameters, CpuidOutputRegisterAdjustments)>,
}

View File

@@ -0,0 +1,880 @@
// Copyright © 2026 Cyberus Technology GmbH
//
// SPDX-License-Identifier: Apache-2.0
//
use hypervisor::CpuVendor;
use hypervisor::arch::x86::CpuIdEntry;
use crate::x86_64::cpu_profile::cpuid_adjustments::{
CpuidOutputRegisterAdjustments, CpuidProfileData, MissingCpuidEntriesError,
};
use crate::x86_64::{AMX_TILECFG_BIT, AMX_TILEDATA_BIT, CpuidReg};
/// Mask indicating availability of the AMX TILECFG state component
const TILECFG_MASK: u32 = 1_u32 << AMX_TILECFG_BIT;
/// Mask indicating availability of the AMX TILEDATA state component
const TILEDATA_MASK: u32 = 1_u32 << AMX_TILEDATA_BIT;
pub mod cpuid_adjustments;
// TODO: Auto generate the CpuProfile enum with a build script once we introduce user facing CPU profiles.
/// A [`CpuProfile`] is a mechanism for ensuring live migration compatibility
/// between hosts with potentially different CPU models.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum CpuProfile {
#[default]
Host,
}
impl CpuProfile {
/// Adjust `cpuid` to the chosen CPU profile.
///
/// The CPUID data obtained from the hypervisor is thus downgraded to the selected profile.
///
/// This method does **not** perform any compatibility checks beyond
/// ensuring that all expected (sub) leaves required by the CPU profile are present.
///
/// The caller is responsible for ensuring compatibility of `cpuid` by the time it is
/// utilized.
///
/// If Intel AMX is not desired, then passing `amx = false` will permit missing (sub)-leaves
/// that are **purely AMX related**.
///
/// The Host profile guarantees that `cpuid` is returned without any modifications.
pub(in crate::x86_64) fn adjust_cpuid(
&self,
cpuid: Vec<CpuIdEntry>,
amx: bool,
cpu_vendor: CpuVendor,
) -> Result<Vec<CpuIdEntry>, MissingCpuidEntriesError> {
let Some(cpuid_profile_data) = self.cpuid_data() else {
return Ok(cpuid);
};
adjust_cpuid(cpuid_profile_data, cpuid, amx, cpu_vendor)
}
/// Obtain CPUID adjustment data related to the CPU profile.
fn cpuid_data(&self) -> Option<CpuidProfileData> {
// TODO: Auto generate this through a build script once
// we introduce actual CPU profiles.
match self {
CpuProfile::Host => None,
}
}
}
/// See [`CpuProfile::adjust_cpuid`](CpuProfile::adjust_cpuid)
fn adjust_cpuid(
CpuidProfileData { mut adjustments }: CpuidProfileData,
cpuid: Vec<CpuIdEntry>,
amx: bool,
cpu_vendor: CpuVendor,
) -> Result<Vec<CpuIdEntry>, MissingCpuidEntriesError> {
if (!amx) && matches!(cpu_vendor, CpuVendor::Intel) {
let amx_tilecfg_leaf = u32::from(AMX_TILECFG_BIT);
let amx_tiledata_leaf = u32::from(AMX_TILEDATA_BIT);
// In this case we invalidate tile state components and zero out all other purely AMX related leaves
// in order to maximize our chances of finding all required (sub) leaves.
for adj in adjustments.iter_mut() {
if adj.0.sub_leaf.start() != adj.0.sub_leaf.end() {
continue;
}
let sub_leaf = *adj.0.sub_leaf.start();
let leaf = adj.0.leaf;
if (leaf == 0xd) && (sub_leaf == 0) && (adj.0.register == CpuidReg::EAX) {
adj.1.mask &= !(TILECFG_MASK | TILEDATA_MASK);
adj.1.replacements &= !(TILECFG_MASK | TILEDATA_MASK);
}
if (leaf == 0xd) && (sub_leaf == 1) && (adj.0.register == CpuidReg::ECX) {
adj.1.mask &= !(TILECFG_MASK | TILEDATA_MASK);
adj.1.replacements &= !(TILECFG_MASK | TILEDATA_MASK);
}
if (leaf == 0xd) && ((sub_leaf == amx_tilecfg_leaf) || (sub_leaf == amx_tiledata_leaf))
{
adj.1.mask = 0;
adj.1.replacements = 0;
}
// Tile Information (purely AMX related).
if leaf == 0x1d {
adj.1.mask = 0;
adj.1.replacements = 0;
}
// TMUL information (purely AMX related)
if leaf == 0x1e {
adj.1.mask = 0;
adj.1.replacements = 0;
}
}
}
CpuidOutputRegisterAdjustments::adjust_cpuid_entries(cpuid, &adjustments)
}
#[cfg(test)]
mod unit_tests {
use proptest::prelude::*;
use super::{CpuIdEntry, CpuVendor, CpuidProfileData, CpuidReg, adjust_cpuid};
use crate::x86_64::cpu_profile::cpuid_adjustments::{
CpuidOutputRegisterAdjustments, CpuidParameters,
};
use crate::x86_64::cpu_profile::{TILECFG_MASK, TILEDATA_MASK};
// Note that the tests for adjust_cpuid within this module tend to use much simpler inputs
// than what it will be called with at runtime within Cloud hypervisor. We do this here in order
// to keep each test focused on the behavioral aspect under test.
/// Helper function that returns adjustments tied to purely AMX related leaves.
fn amx_related_adjustments() -> Vec<(CpuidParameters, CpuidOutputRegisterAdjustments)> {
let amx_adjustments_json = r#"
[
[
{
"leaf": "0xd",
"sub_leaf": {
"start": "0x11",
"end": "0x11"
},
"register": "EAX"
},
{
"replacements": "0x40",
"mask": "0x0"
}
],
[
{
"leaf": "0xd",
"sub_leaf": {
"start": "0x11",
"end": "0x11"
},
"register": "EBX"
},
{
"replacements": "0xac0",
"mask": "0x0"
}
],
[
{
"leaf": "0xd",
"sub_leaf": {
"start": "0x11",
"end": "0x11"
},
"register": "ECX"
},
{
"replacements": "0x2",
"mask": "0x0"
}
],
[
{
"leaf": "0xd",
"sub_leaf": {
"start": "0x12",
"end": "0x12"
},
"register": "EAX"
},
{
"replacements": "0x2000",
"mask": "0x0"
}
],
[
{
"leaf": "0xd",
"sub_leaf": {
"start": "0x12",
"end": "0x12"
},
"register": "EBX"
},
{
"replacements": "0xb00",
"mask": "0x0"
}
],
[
{
"leaf": "0xd",
"sub_leaf": {
"start": "0x12",
"end": "0x12"
},
"register": "ECX"
},
{
"replacements": "0x6",
"mask": "0x0"
}
],
[
{
"leaf": "0x1d",
"sub_leaf": {
"start": "0x0",
"end": "0x0"
},
"register": "EAX"
},
{
"replacements": "0x1",
"mask": "0x0"
}
],
[
{
"leaf": "0x1d",
"sub_leaf": {
"start": "0x1",
"end": "0x1"
},
"register": "EAX"
},
{
"replacements": "0x4002000",
"mask": "0x0"
}
],
[
{
"leaf": "0x1d",
"sub_leaf": {
"start": "0x1",
"end": "0x1"
},
"register": "EBX"
},
{
"replacements": "0x80040",
"mask": "0x0"
}
],
[
{
"leaf": "0x1d",
"sub_leaf": {
"start": "0x1",
"end": "0x1"
},
"register": "ECX"
},
{
"replacements": "0x10",
"mask": "0x0"
}
],
[
{
"leaf": "0x1e",
"sub_leaf": {
"start": "0x0",
"end": "0x0"
},
"register": "EAX"
},
{
"replacements": "0x0",
"mask": "0x0"
}
],
[
{
"leaf": "0x1e",
"sub_leaf": {
"start": "0x0",
"end": "0x0"
},
"register": "EBX"
},
{
"replacements": "0x4010",
"mask": "0x0"
}
]
]"#;
serde_json::from_str(amx_adjustments_json).unwrap()
}
// Randonly generate three CPUID entries and construct some simple adjustments which we apply
// through the `adjust_cpuid` function and assert that our expectations are met.
proptest! {
#[test]
fn adjust_cpuid_simple_adjustments(
leaf0 in any::<u32>(),
leaf1 in any::<u32>(),
leaf2 in any::<u32>(),
a in any::<u32>(),
b in any::<u32>(),
c in any::<u32>(),
d in any::<u32>(),
) {
// Ensure that we have distinct leaves in this test
let mut leaves = [leaf0, leaf1, leaf2];
leaves.sort_unstable();
for (l, i) in leaves.iter_mut().zip([0, 1, 2]) {
*l = l.wrapping_add(i);
}
let [leaf0, leaf1, leaf2] = leaves;
// The following leaves have some special handling that we test in later
// more specialized tests. In this simple test we want to avoid them.
let leaves_with_special_handling = { [0xd, 0x1d, 0x1e] };
let transform_leaf = |leaf: u32| {
if leaves_with_special_handling.contains(&leaf) {
// Ensures that we get a leaf different from any of the leaves that have special handling
leaf | 0x1000
} else {
leaf
}
};
let leaf0 = transform_leaf(leaf0);
let leaf1 = transform_leaf(leaf1);
let leaf2 = transform_leaf(leaf2);
// The leaves should still be distinct
assert!(leaf0 != leaf1);
assert!(leaf0 != leaf2);
assert!(leaf1 != leaf2);
// We have now setup leaves to be used in this test
// Let's now construct some simple adjustments
// mask retaining bits 0,1,2 and 3
let first_four_bits_mask = 15;
// Retain the first four bits of the register and overwrite the remaining bits with the value "42"
let adjustment_u = CpuidOutputRegisterAdjustments {
mask: first_four_bits_mask,
replacements: 42 << 4,
};
let assert_adjustment_u = |new_value: u32, old_value: u32| {
assert_eq!(new_value & first_four_bits_mask, (old_value & first_four_bits_mask));
// Recall that we placed the value 42 into bits 31:4
assert_eq!(new_value >> 4, 42);
};
// Set bits 0 and 28 and zero out the rest
let adjustment_v = CpuidOutputRegisterAdjustments {
replacements: 1 | (1 << 28),
mask: 0
};
let assert_adjustment_v = |new_value: u32| {
assert_eq!(new_value, 1 | (1 << 28));
};
// Make adjustment_u apply to EAX of leaf0 and EBX of leaf1.
//
// Make adjustment_v apply to EDX of leaf0 and ECX of leaf1.
//
// We do not specify any adjustment for leaf2.
let cpuid_profile_data = CpuidProfileData {
adjustments: vec![
(
CpuidParameters {
leaf: leaf0,
sub_leaf: 0..=0,
register: CpuidReg::EAX,
},
adjustment_u,
),
(
CpuidParameters {
leaf: leaf0,
sub_leaf: 0..=0,
register: CpuidReg::EDX,
},
adjustment_v,
),
(
CpuidParameters {
leaf: leaf1,
sub_leaf: 0..=0,
register: CpuidReg::EBX,
},
adjustment_u,
),
(
CpuidParameters {
leaf: leaf1,
sub_leaf: 0..=0,
register: CpuidReg::ECX,
},
adjustment_v,
),
],
};
// Construct cpuid entries consisting of leaves leaf0, leaf1 and leaf2.
// The registers eax, ebx, ecx, edx are populated with the randomly generated values `a`, `b`, `c` and `d`
// and we do not consider sub-leaves in this test.
let cpuid = vec![
CpuIdEntry {
function: leaf0,
index: 0,
flags: 0,
eax: a,
ebx: b,
ecx: c,
edx: d,
},
CpuIdEntry {
function: leaf1,
index: 0,
flags: 0,
eax: a,
ebx: b,
ecx: c,
edx: d,
},
CpuIdEntry {
function: leaf2,
index: 0,
flags: 0,
eax: a,
ebx: b,
ecx: c,
edx: d,
},
];
// Check that the output of `adjust_cpuid` contains the same CPUID leaves as the
// `cpuid` vector we started with.
let expected_num_entries = cpuid.len();
let mut found_entry_count = 0;
let adjusted_cpuid =
adjust_cpuid(cpuid_profile_data, cpuid, false, CpuVendor::Intel).unwrap();
// Iterate through our adjusted entries and assert that our expectations are met.
for entry in adjusted_cpuid {
let CpuIdEntry {
function,
index,
flags,
eax,
ebx,
ecx,
edx,
} = entry;
if function == leaf0 {
found_entry_count += 1;
assert_adjustment_u(eax, a);
assert_adjustment_v(edx);
// ebx and ecx should be zeroed out
assert_eq!(ebx, 0);
assert_eq!(ecx, 0);
}
if function == leaf1 {
found_entry_count += 1;
assert_adjustment_u(ebx, b);
assert_adjustment_v(ecx);
// eax and edx should be zeroed out
assert_eq!(eax, 0);
assert_eq!(edx, 0);
}
if function == leaf2 {
found_entry_count += 1;
// All registers should be zeroed out
assert_eq!(eax, ebx);
assert_eq!(ebx, ecx);
assert_eq!(ecx, edx);
assert_eq!(edx, 0);
}
// Index and flags should not be altered. Since these were both
// always 0 for all leaves in the original `cpuid` that should
// remain the case.
assert_eq!(index, 0);
assert_eq!(flags, 0);
}
assert_eq!(expected_num_entries, found_entry_count);
}
}
// Check that adjust_cpuid follows the prescribed adjustments on
// specified subleaf ranges
#[test]
fn adjust_cpuid_works_with_subleaf_ranges() {
// As in the real runtime case the Topology enumeration leaves should not be altered
// by CPU profiles. In this test we thus define adjustment's that do not alter non-reserved
// bits for the 0x1f leaf and its sub-leaves.
let adjustments_json = r#"
[
[
{
"leaf": "0x1f",
"sub_leaf": {
"start": "0x0",
"end": "0xffffffff"
},
"register": "EAX"
},
{
"replacements": "0x0",
"mask": "0x1f"
}
],
[
{
"leaf": "0x1f",
"sub_leaf": {
"start": "0x0",
"end": "0xffffffff"
},
"register": "EBX"
},
{
"replacements": "0x0",
"mask": "0xffff"
}
],
[
{
"leaf": "0x1f",
"sub_leaf": {
"start": "0x0",
"end": "0xffffffff"
},
"register": "ECX"
},
{
"replacements": "0x0",
"mask": "0xffff"
}
],
[
{
"leaf": "0x1f",
"sub_leaf": {
"start": "0x0",
"end": "0xffffffff"
},
"register": "EDX"
},
{
"replacements": "0x0",
"mask": "0xffffffff"
}
]
]"#;
let cpuid_profile_data = CpuidProfileData {
adjustments: serde_json::from_str(adjustments_json).unwrap(),
};
let cpuid = vec![
CpuIdEntry {
function: 0x1f,
index: 0,
flags: 1,
eax: 0x00000001,
ebx: 0x00000002,
ecx: 0x00000100,
edx: 0x00000000,
},
CpuIdEntry {
function: 0x1f,
index: 1,
flags: 1,
eax: 0x00000004,
ebx: 0x00000008,
ecx: 0x00000201,
edx: 0x00000006,
},
];
let adjusted_cpuid =
adjust_cpuid(cpuid_profile_data, cpuid.clone(), false, CpuVendor::Intel).unwrap();
// Check that cpuid was indeed not altered
for entry in cpuid {
let adjusted_entry = adjusted_cpuid
.iter()
.find(|e| (e.function == entry.function) && (e.index == entry.index))
.unwrap();
assert_eq!(entry.eax, adjusted_entry.eax);
assert_eq!(entry.ebx, adjusted_entry.ebx);
assert_eq!(entry.ecx, adjusted_entry.ecx);
assert_eq!(entry.edx, adjusted_entry.edx);
}
}
#[test]
fn adjust_cpuid_fails_on_missing_entries() {
let cpuid = vec![CpuIdEntry {
function: 0x0,
index: 0x0,
eax: 0x20,
ebx: 0x756e6547,
ecx: 0x6c65746e,
edx: 0x49656e69,
flags: 0,
}];
let cpuid_profile_data = CpuidProfileData {
adjustments: vec![(
CpuidParameters {
leaf: 0x1,
sub_leaf: 0x0..=0x0,
register: CpuidReg::EAX,
},
CpuidOutputRegisterAdjustments {
replacements: 0x000806f8,
mask: 0,
},
)],
};
let _ =
adjust_cpuid(cpuid_profile_data, cpuid.clone(), false, CpuVendor::Intel).unwrap_err();
// Also check this for a purely AMX related leaves which has special handling
let _ = adjust_cpuid(
CpuidProfileData {
adjustments: amx_related_adjustments(),
},
cpuid,
true,
CpuVendor::Intel,
)
.unwrap_err();
}
// Check that if `amx = false` then AMX related leaves are zeroed out
#[test]
fn adjust_cpuid_no_amx_zeros_amx_leaves() {
// Not AMX related
let leaf_zero = CpuIdEntry {
function: 0x0,
index: 0x0,
eax: 0x20,
ebx: 0x756e6547,
ecx: 0x6c65746e,
edx: 0x49656e69,
flags: 0,
};
let cpuid = vec![
leaf_zero,
// State components base leaf extracted from granite rapids the AMX related state component bits are set
CpuIdEntry {
function: 0xd,
index: 0x0,
flags: 1,
eax: 0x000602e7,
ebx: 0x00002b00,
ecx: 0x00002b00,
edx: 0x00000000,
},
// TILECFG state
CpuIdEntry {
function: 0xd,
index: 0x11,
flags: 1,
eax: 0x00000040,
ebx: 0x00000ac0,
ecx: 0x00000002,
edx: 0x00000000,
},
// TILEDATA state
CpuIdEntry {
function: 0xd,
index: 0x12,
flags: 1,
eax: 0x00002000,
ebx: 0x00000b00,
ecx: 0x00000006,
edx: 0x00000000,
},
// Tile information base leaf
CpuIdEntry {
function: 0x1d,
index: 0x0,
flags: 1,
eax: 0x00000001,
ebx: 0x00000000,
ecx: 0x00000000,
edx: 0x00000000,
},
// Tile Palette 1
CpuIdEntry {
function: 0x1d,
index: 0x1,
flags: 1,
eax: 0x04002000,
ebx: 0x00080040,
ecx: 0x00000010,
edx: 0x00000000,
},
// TMUL information base leaf
CpuIdEntry {
function: 0x1e,
index: 0x0,
flags: 1,
eax: 0x00000000,
ebx: 0x00004010,
ecx: 0x00000000,
edx: 0x00000000,
},
];
let adjustments: Vec<(CpuidParameters, CpuidOutputRegisterAdjustments)> =
amx_related_adjustments()
.into_iter()
// leave leaf 0 untouched
.chain([
(
CpuidParameters {
leaf: 0x0,
sub_leaf: 0x0..=0x0,
register: CpuidReg::EAX,
},
CpuidOutputRegisterAdjustments {
replacements: 0,
mask: u32::MAX,
},
),
(
CpuidParameters {
leaf: 0x0,
sub_leaf: 0x0..=0x0,
register: CpuidReg::EBX,
},
CpuidOutputRegisterAdjustments {
replacements: 0,
mask: u32::MAX,
},
),
(
CpuidParameters {
leaf: 0x0,
sub_leaf: 0x0..=0x0,
register: CpuidReg::ECX,
},
CpuidOutputRegisterAdjustments {
replacements: 0,
mask: u32::MAX,
},
),
(
CpuidParameters {
leaf: 0x0,
sub_leaf: 0x0..=0x0,
register: CpuidReg::EDX,
},
CpuidOutputRegisterAdjustments {
replacements: 0,
mask: u32::MAX,
},
),
])
// Keep EAX of leaf 0xd so we see that the AMX-related state component bits get unset, regardless of what
// the adjustment says
.chain([(
CpuidParameters {
leaf: 0xd,
sub_leaf: 0x0..=0x0,
register: CpuidReg::EAX,
},
CpuidOutputRegisterAdjustments {
mask: u32::MAX,
replacements: 0,
},
)])
.collect();
let adjusted_cpuid = adjust_cpuid(
CpuidProfileData { adjustments },
cpuid.clone(),
false,
CpuVendor::Intel,
)
.unwrap();
// Check that leaf zero is left untouched as expected
{
let adjusted_leaf_zero = adjusted_cpuid
.iter()
.find(|entry| entry.function == 0x0)
.unwrap();
assert_eq!(adjusted_leaf_zero.eax, leaf_zero.eax);
assert_eq!(adjusted_leaf_zero.ebx, leaf_zero.ebx);
assert_eq!(adjusted_leaf_zero.ecx, leaf_zero.ecx);
assert_eq!(adjusted_leaf_zero.edx, leaf_zero.edx);
}
// Check that the TILECFG and TILEDATA state bits are now zeroed ut
{
let state_cmp_base_leaf = adjusted_cpuid
.iter()
.find(|entry| (entry.function == 0xd) && (entry.index == 0x0))
.unwrap();
// EAX should not have been zeroed out in its entirety
assert!(state_cmp_base_leaf.eax != 0);
// The TILECFG state bit should be unset
assert_eq!(state_cmp_base_leaf.eax & TILECFG_MASK, 0);
// The TILEDATA state bit should be unset
assert_eq!(state_cmp_base_leaf.eax & TILEDATA_MASK, 0);
}
// Since all remaining entries we placed in `cpuid` are purely AMX related we now
// expect them to be zeroed out
for entry in adjusted_cpuid {
if entry.function == 0 || (entry.function == 0xd && entry.index == 0x0) {
continue;
}
assert_eq!(entry.eax, 0);
assert_eq!(entry.ebx, 0);
assert_eq!(entry.ecx, 0);
assert_eq!(entry.edx, 0);
}
}
// Check that if `amx = false` then missing purely AMX related leaves
// do not lead to failure
#[test]
fn adjust_cpuid_no_amx_missing_amx_leaves_accepted() {
let cpuid = vec![CpuIdEntry {
function: 0x0,
index: 0x0,
eax: 0x20,
ebx: 0x756e6547,
ecx: 0x6c65746e,
edx: 0x49656e69,
flags: 0,
}];
let _ = adjust_cpuid(
CpuidProfileData {
adjustments: amx_related_adjustments(),
},
cpuid,
false,
CpuVendor::Intel,
)
.unwrap();
}
}

View File

@@ -0,0 +1,82 @@
// Copyright © 2026 Cyberus Technology GmbH
//
// SPDX-License-Identifier: Apache-2.0
//
use std::result;
use serde::{Deserialize, Deserializer, Serializer, de};
/// Serializes the given `input` as a hex string (starting with "0x").
///
/// As an example if `input:=5` then this function will feed the given
/// `serializer` the string "0x5".
pub(crate) fn serialize_u32_hex<S: Serializer>(
input: &u32,
serializer: S,
) -> result::Result<S::Ok, S::Error> {
serializer.serialize_str(&format!("{input:#x}"))
}
/// Deserializes a u32 from a hex string representation.
pub(crate) fn deserialize_u32_hex<'de, D: Deserializer<'de>>(
deserializer: D,
) -> result::Result<u32, D::Error> {
let hex: &str = <&str>::deserialize(deserializer)?;
u32::from_str_radix(hex.strip_prefix("0x").unwrap_or(""), 16).map_err(|_| {
<D::Error as de::Error>::custom(format!("{hex} is not a hex encoded 32 bit integer"))
})
}
#[cfg(test)]
mod unit_tests {
use proptest::prelude::*;
use serde::{Deserialize, Serialize};
use super::*;
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
struct TestStruct {
#[serde(
serialize_with = "serialize_u32_hex",
deserialize_with = "deserialize_u32_hex"
)]
foo: u32,
#[serde(
serialize_with = "serialize_u32_hex",
deserialize_with = "deserialize_u32_hex"
)]
bar: u32,
}
// Check that our hex serializers satisfy the two following invariants
// 1. Serialization followed by deserialization is the identity.
// 2. Values of type u32 are serialized to strings starting with "0x" and then
// a sub-string where all characters are ascii hex digits (with the letters [a-f] always in lowercase).
proptest! {
#[test]
fn hex_serialization_works(foo in any::<u32>(), bar in any::<u32>()) {
let t = TestStruct { foo , bar };
let t_string = serde_json::to_string(&t).unwrap();
let t_deserialized = serde_json::from_str(&t_string).unwrap();
prop_assert_eq!(t, t_deserialized);
let t_json = serde_json::to_value(t).unwrap();
let check_str_invariants = |value: &str| {
prop_assert!(value.starts_with("0x"));
prop_assert!(value.as_bytes()[2..].iter().all(u8::is_ascii_hexdigit));
prop_assert!(!value.as_bytes()[2..].iter().any(u8::is_ascii_uppercase));
Ok(())
};
let foo_str = t_json.get("foo").unwrap().as_str().unwrap();
let bar_str = t_json.get("bar").unwrap().as_str().unwrap();
check_str_invariants(foo_str)?;
check_str_invariants(bar_str)?;
}
}
}

View File

@@ -7,6 +7,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
pub mod cpu_profile;
pub mod interrupts;
pub mod layout;
pub mod regs;
@@ -14,13 +15,14 @@ pub mod regs;
#[cfg(feature = "tdx")]
pub mod tdx;
mod helpers;
mod mpspec;
mod mptable;
mod smbios;
use std::arch::x86_64;
use std::mem;
use helpers::{deserialize_u32_hex, serialize_u32_hex};
use hypervisor::arch::x86::{CPUID_FLAG_VALID_INDEX, CpuIdEntry};
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
use linux_loader::loader::bootparam::{boot_params, setup_header};
@@ -28,13 +30,16 @@ use linux_loader::loader::elf::start_info::{
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
};
use log::{debug, error, info};
pub use smbios::{SmbiosChassisConfig, SmbiosConfig, SmbiosSystem};
use thiserror::Error;
use vm_memory::{
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
GuestMemoryRegion,
};
use vmm_sys_util::fam;
use crate::{GuestMemoryMmap, InitramfsConfig, RegionType};
use crate::x86_64::cpu_profile::cpuid_adjustments::MissingCpuidEntriesError;
use crate::{CpuProfile, GuestMemoryMmap, InitramfsConfig, RegionType};
// While modern architectures support more than 255 CPUs via x2APIC,
// legacy devices such as mptable support at most 254 CPUs.
@@ -55,6 +60,9 @@ const AMX_INT8: u8 = 25; // AMX tile computation on 8-bit integers
const AMX_FP16: u8 = 21; // AMX tile computation on fp16 numbers
const AMX_COMPLEX: u8 = 8; // AMX tile computation on complex numbers
const AMX_TILECFG_BIT: u8 = 17; // AMX tile cfg state component bit
const AMX_TILEDATA_BIT: u8 = 18; // AMX tile data state component bit
// KVM feature bits
#[cfg(feature = "tdx")]
const KVM_FEATURE_CLOCKSOURCE_BIT: u8 = 0;
@@ -93,6 +101,7 @@ pub struct CpuidConfig {
#[cfg(feature = "tdx")]
pub tdx: bool,
pub amx: bool,
pub profile: CpuProfile,
}
#[derive(Debug, Error)]
@@ -135,16 +144,32 @@ pub enum Error {
/// Error populating CPUID with KVM HyperV emulation details
#[error("Error populating CPUID with KVM HyperV emulation details")]
CpuidKvmHyperV(#[source] vmm_sys_util::fam::Error),
CpuidKvmHyperV(#[source] fam::Error),
/// Error populating CPUID with CPU identification
#[error("Error populating CPUID with CPU identification")]
CpuidIdentification(#[source] vmm_sys_util::fam::Error),
CpuidIdentification(#[source] fam::Error),
/// Error checking CPUID compatibility
#[error("Error checking CPUID compatibility")]
CpuidCheckCompatibility,
/// Error checking if CPUID is compatible with profile
#[error(
"The selected CPU profile cannot be utilized because the host's CPUID entries are not compatible with the profile"
)]
CpuProfileCpuidIncompatibility,
/// Error because TDX cannot be enabled when a custom (non host) CPU profile has been selected
#[error("TDX cannot be enabled when a custom CPU profile has been selected")]
CpuProfileTdxIncompatibility,
/// Error when trying to apply a CPU profile because a necessary CPUID entry was not found
#[error(
"The selected CPU profile cannot be utilized because a necessary CPUID entry was not found"
)]
MissingExpectedCpuidEntry(#[source] MissingCpuidEntriesError),
// Error writing EBDA address
#[error("Error writing EBDA address")]
EbdaSetup(#[source] vm_memory::GuestMemoryError),
@@ -190,7 +215,7 @@ pub fn get_max_x2apic_id(topology: (u16, u16, u16, u16)) -> u32 {
)
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
pub enum CpuidReg {
EAX,
EBX,
@@ -502,11 +527,27 @@ impl CpuidFeatureEntry {
features
}
// The function returns `Error` (a.k.a. "incompatible"), when the CPUID features from `src_vm_cpuid`
// is not a subset of those of the `dest_vm_cpuid`.
/// The function returns `Error` (a.k.a. "incompatible"), when the CPUID features from `src_vm_cpuid`
/// is not a subset of those of the `dest_vm_cpuid`.
pub fn check_cpuid_compatibility(
src_vm_cpuid: &[CpuIdEntry],
dest_vm_cpuid: &[CpuIdEntry],
) -> Result<(), Error> {
Self::check_cpuid_compatibility_with_descriptions(
src_vm_cpuid,
"source VM",
dest_vm_cpuid,
"destination VM",
)
}
/// Similar to `check_cpuid_compatibility`, but with the possibility to change
/// the description of the source and destination for logging purposes.
fn check_cpuid_compatibility_with_descriptions(
src_vm_cpuid: &[CpuIdEntry],
src_description: &str,
dest_vm_cpuid: &[CpuIdEntry],
dest_description: &str,
) -> Result<(), Error> {
let feature_entry_list = &Self::checked_feature_entry_list();
let src_vm_features = Self::get_features_from_cpuid(src_vm_cpuid, feature_entry_list);
@@ -532,8 +573,8 @@ impl CpuidFeatureEntry {
};
if !entry_compatible {
error!(
"Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \
compatible_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
"Detected incompatible CPUID entry: leaf={:#04x} (subleaf={:#04x}), register='{:?}', \
compatible_check='{:?}', {src_description} feature='{:#04x}', {dest_description} feature='{:#04x}'.",
entry.function,
entry.index,
entry.feature_reg,
@@ -555,10 +596,20 @@ impl CpuidFeatureEntry {
}
}
/// Generate the CPUID entries intended for every vCPU.
///
/// ## CPU profiles
///
/// This function takes the CPU profile given in `config` into account and returns compatible CPUID entries
/// if possible.
///
/// An error is returned when the CPUID entries obtained from the hypervisor do not satisfy the requirements
/// to apply the selected CPU profile.
pub fn generate_common_cpuid(
hypervisor: &dyn hypervisor::Hypervisor,
config: &CpuidConfig,
) -> super::Result<Vec<CpuIdEntry>> {
#[allow(unused_unsafe)]
// SAFETY: cpuid called with valid leaves
if unsafe { x86_64::__cpuid(1) }.ecx & (1 << HYPERVISOR_ECX_BIT) == 1 << HYPERVISOR_ECX_BIT {
// SAFETY: cpuid called with valid leaves
@@ -576,9 +627,86 @@ pub fn generate_common_cpuid(
}
info!(
"Generating guest CPUID for with physical address size: {}",
"Generating guest CPUID with physical address size: {}",
config.phys_bits
);
// Supported CPUID
let mut cpuid = hypervisor
.get_supported_cpuid()
.map_err(Error::CpuidGetSupported)?;
let is_non_host_profile = !matches!(config.profile, CpuProfile::Host);
#[cfg(feature = "tdx")]
if config.tdx {
if is_non_host_profile {
// TDX is not supported by CPU profiles other than host for the time being.
return Err(Error::CpuProfileTdxIncompatibility.into());
}
common_cpuid_tdx_configuration(&mut cpuid, hypervisor)?;
}
// Copy CPU identification string
for i in 0x8000_0002..=0x8000_0004 {
cpuid.retain(|c| c.function != i);
// SAFETY: call cpuid with valid leaves
#[allow(unused_unsafe)]
let leaf = unsafe { x86_64::__cpuid(i) };
cpuid.push(CpuIdEntry {
function: i,
eax: leaf.eax,
ebx: leaf.ebx,
ecx: leaf.ecx,
edx: leaf.edx,
..Default::default()
});
}
let cpuid_profile = if is_non_host_profile {
let cpuid_profile = config
.profile
.adjust_cpuid(cpuid.clone(), config.amx, hypervisor.get_cpu_vendor())
.map_err(Error::MissingExpectedCpuidEntry)?;
required_common_cpuid_updates(
cpuid_profile,
config,
#[cfg(feature = "kvm")]
hypervisor.hypervisor_type(),
)
} else {
Vec::new()
};
let cpuid_host = required_common_cpuid_updates(
cpuid,
config,
#[cfg(feature = "kvm")]
hypervisor.hypervisor_type(),
);
// If we want to apply a CPU profile we need to check that it remains compatible with `cpuid_host`
if is_non_host_profile {
CpuidFeatureEntry::check_cpuid_compatibility_with_descriptions(
&cpuid_profile,
"CPU Profile",
&cpuid_host,
"Host VM",
)
.map_err(|_| Error::CpuProfileCpuidIncompatibility)?;
Ok(cpuid_profile)
} else {
Ok(cpuid_host)
}
}
/// Apply updates to common CPUID (not vCPU specific) that are necessary regardless of
/// the chosen CPU profile.
fn required_common_cpuid_updates(
mut cpuid: Vec<CpuIdEntry>,
config: &CpuidConfig,
#[cfg(feature = "kvm")] hypervisor_type: hypervisor::HypervisorType,
) -> Vec<CpuIdEntry> {
#[allow(unused_mut)]
let mut cpuid_patches = vec![
// Patch hypervisor bit
@@ -604,10 +732,7 @@ pub fn generate_common_cpuid(
];
#[cfg(feature = "kvm")]
if matches!(
hypervisor.hypervisor_type(),
hypervisor::HypervisorType::Kvm
) {
if matches!(hypervisor_type, hypervisor::HypervisorType::Kvm) {
// Patch tsc deadline timer bit
cpuid_patches.push(CpuidPatch {
function: 1,
@@ -620,108 +745,74 @@ pub fn generate_common_cpuid(
});
}
// Supported CPUID
let mut cpuid = hypervisor
.get_supported_cpuid()
.map_err(Error::CpuidGetSupported)?;
CpuidPatch::patch_cpuid(&mut cpuid, &cpuid_patches);
#[cfg(feature = "tdx")]
let tdx_capabilities = if config.tdx {
let caps = hypervisor
.tdx_capabilities()
.map_err(Error::TdxCapabilities)?;
info!("TDX capabilities {caps:#?}");
Some(caps)
} else {
None
};
// Update some existing CPUID
for entry in cpuid.as_mut_slice().iter_mut() {
#[allow(unused_unsafe)]
match entry.function {
// Clear AMX related bits if the AMX feature is not enabled
0x7 => {
if !config.amx {
if entry.index == 0 {
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8));
}
if entry.index == 1 {
entry.eax &= !(1 << AMX_FP16);
entry.edx &= !(1 << AMX_COMPLEX);
}
0x7 if !config.amx => {
if entry.index == 0 {
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8));
}
if entry.index == 1 {
entry.eax &= !(1 << AMX_FP16);
entry.edx &= !(1 << AMX_COMPLEX);
}
}
0xd =>
{
#[cfg(feature = "tdx")]
if let Some(caps) = &tdx_capabilities {
let xcr0_mask: u64 = 0x82ff;
let xss_mask: u64 = !xcr0_mask;
if entry.index == 0 {
entry.eax &= (caps.xfam_fixed0 as u32) & (xcr0_mask as u32);
entry.eax |= (caps.xfam_fixed1 as u32) & (xcr0_mask as u32);
entry.edx &= ((caps.xfam_fixed0 & xcr0_mask) >> 32) as u32;
entry.edx |= ((caps.xfam_fixed1 & xcr0_mask) >> 32) as u32;
} else if entry.index == 1 {
entry.ecx &= (caps.xfam_fixed0 as u32) & (xss_mask as u32);
entry.ecx |= (caps.xfam_fixed1 as u32) & (xss_mask as u32);
entry.edx &= ((caps.xfam_fixed0 & xss_mask) >> 32) as u32;
entry.edx |= ((caps.xfam_fixed1 & xss_mask) >> 32) as u32;
}
}
// Tile Information (purely AMX related).
0x1d if !config.amx => {
entry.eax = 0;
entry.ebx = 0;
entry.ecx = 0;
entry.edx = 0;
}
0x1d => {
// Tile Information (purely AMX related).
if !config.amx {
entry.eax = 0;
entry.ebx = 0;
entry.ecx = 0;
entry.edx = 0;
}
}
0x1e => {
// TMUL information (purely AMX related)
if !config.amx {
entry.eax = 0;
entry.ebx = 0;
entry.ecx = 0;
entry.edx = 0;
}
// TMUL information (purely AMX related)
0x1e if !config.amx => {
entry.eax = 0;
entry.ebx = 0;
entry.ecx = 0;
entry.edx = 0;
}
// Copy host L1 cache details if not populated by KVM
0x8000_0005 => {
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
0x8000_0005
if entry.eax == 0
&& entry.ebx == 0
&& entry.ecx == 0
&& entry.edx == 0
// SAFETY: cpuid called with valid leaves
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
// SAFETY: cpuid called with valid leaves
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0005) };
entry.eax = leaf.eax;
entry.ebx = leaf.ebx;
entry.ecx = leaf.ecx;
entry.edx = leaf.edx;
}
}
&& unsafe { x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 =>
{
// SAFETY: cpuid called with valid leaves
let leaf = unsafe { x86_64::__cpuid(0x8000_0005) };
entry.eax = leaf.eax;
entry.ebx = leaf.ebx;
entry.ecx = leaf.ecx;
entry.edx = leaf.edx;
}
// Copy host L2 cache details if not populated by KVM
0x8000_0006 => {
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
0x8000_0006
if entry.eax == 0
&& entry.ebx == 0
&& entry.ecx == 0
&& entry.edx == 0
// SAFETY: cpuid called with valid leaves
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
// SAFETY: cpuid called with valid leaves
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
entry.eax = leaf.eax;
entry.ebx = leaf.ebx;
entry.ecx = leaf.ecx;
entry.edx = leaf.edx;
}
}
&& unsafe { x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 =>
{
// SAFETY: cpuid called with valid leaves
let leaf = unsafe { x86_64::__cpuid(0x8000_0006) };
entry.eax = leaf.eax;
entry.ebx = leaf.ebx;
entry.ecx = leaf.ecx;
entry.edx = leaf.edx;
}
// Set CPU physical bits
// Set CPU physical bits and guest physical bits
0x8000_0008 => {
entry.eax = (entry.eax & 0xffff_ff00) | (config.phys_bits as u32 & 0xff);
entry.eax = (entry.eax & 0xff00_ff00)
| (config.phys_bits as u32 & 0xff)
| ((config.phys_bits as u32 & 0xff) << 16);
}
0x4000_0001 => {
// Enable KVM_FEATURE_MSI_EXT_DEST_ID. This allows the guest to target
@@ -743,21 +834,6 @@ pub fn generate_common_cpuid(
}
}
// Copy CPU identification string
for i in 0x8000_0002..=0x8000_0004 {
cpuid.retain(|c| c.function != i);
// SAFETY: call cpuid with valid leaves
let leaf = unsafe { std::arch::x86_64::__cpuid(i) };
cpuid.push(CpuIdEntry {
function: i,
eax: leaf.eax,
ebx: leaf.ebx,
ecx: leaf.ecx,
edx: leaf.edx,
..Default::default()
});
}
if config.kvm_hyperv {
// Remove conflicting entries
cpuid.retain(|c| c.function != 0x4000_0000);
@@ -785,16 +861,31 @@ pub fn generate_common_cpuid(
});
cpuid.push(CpuIdEntry {
function: 0x4000_0003,
eax: (1 << 1) // AccessPartitionReferenceCounter
eax: (1 << 0) // AccessVpRunTimeReg
| (1 << 1) // AccessPartitionReferenceCounter
| (1 << 2) // AccessSynicRegs
| (1 << 3) // AccessSyntheticTimerRegs
| (1 << 9), // AccessPartitionReferenceTsc
edx: 1 << 3, // CPU dynamic partitioning
| (1 << 4) // AccessIntrCtrlRegs (APIC access MSRs / VP Assist EOI)
| (1 << 5) // AccessHypercallMsrs
| (1 << 6) // AccessVpIndex
| (1 << 9) // AccessPartitionReferenceTsc
| (1 << 11), // AccessFrequencyMsrs (TSC/APIC frequency MSRs)
edx: (1 << 3) // CPU dynamic partitioning
| (1 << 4) // FastHypercall (XMM register hypercall input)
| (1 << 8), // ExtendedGvaRangesForFlushVirtualAddressList
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x4000_0004,
eax: 1 << 5, // Recommend relaxed timing
// Recommendation hints to Hyper-V-aware guests. Bit semantics per
// Microsoft Hypervisor Top-Level Functional Specification 7.4.5.
eax: (1 << 1) // LocalTlbFlushRecommended
| (1 << 2) // RemoteTlbFlushRecommended
| (1 << 3) // ApicAccessRecommended (VP Assist page MSR EOI/ICR/TPR)
| (1 << 5) // RelaxedTimingRecommended
| (1 << 9) // DeprecatingAeoiRecommended (keeps APICv on with SynIC)
| (1 << 10), // ClusterIpiRecommended (HvCallSendSyntheticClusterIpi)
ebx: 0xfff, // Suggested spinlock retry attempts before trapping to host
..Default::default()
});
for i in 0x4000_0005..=0x4000_000a {
@@ -805,10 +896,39 @@ pub fn generate_common_cpuid(
}
}
Ok(cpuid)
cpuid
}
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "tdx")]
fn common_cpuid_tdx_configuration(
cpuid: &mut [CpuIdEntry],
hypervisor: &dyn hypervisor::Hypervisor,
) -> super::Result<()> {
let caps = hypervisor
.tdx_capabilities()
.map_err(Error::TdxCapabilities)?;
info!("TDX capabilities {caps:#?}");
for entry in cpuid.iter_mut().filter(|entry| entry.function == 0xd) {
let xcr0_mask: u64 = 0x82ff;
let xss_mask: u64 = !xcr0_mask;
if entry.index == 0 {
entry.eax &= (caps.xfam_fixed0 as u32) & (xcr0_mask as u32);
entry.eax |= (caps.xfam_fixed1 as u32) & (xcr0_mask as u32);
entry.edx &= ((caps.xfam_fixed0 & xcr0_mask) >> 32) as u32;
entry.edx |= ((caps.xfam_fixed1 & xcr0_mask) >> 32) as u32;
} else if entry.index == 1 {
entry.ecx &= (caps.xfam_fixed0 as u32) & (xss_mask as u32);
entry.ecx |= (caps.xfam_fixed1 as u32) & (xss_mask as u32);
entry.edx &= ((caps.xfam_fixed0 & xss_mask) >> 32) as u32;
entry.edx |= ((caps.xfam_fixed1 & xss_mask) >> 32) as u32;
}
}
Ok(())
}
#[expect(clippy::too_many_arguments)]
pub fn configure_vcpu(
vcpu: &dyn hypervisor::Vcpu,
id: u32,
@@ -818,6 +938,7 @@ pub fn configure_vcpu(
cpu_vendor: CpuVendor,
topology: (u16, u16, u16, u16),
nested: bool,
setup_registers: bool,
) -> super::Result<()> {
let x2apic_id = get_x2apic_id(id, Some(topology));
@@ -836,11 +957,13 @@ pub fn configure_vcpu(
entry.ebx &= 0xffffff;
entry.ebx |= x2apic_id << 24;
apic_id_patched = true;
if !nested {
// Disable nested virtualization for Intel
entry.ecx &= !(1 << VMX_ECX_BIT);
if matches!(cpu_vendor, CpuVendor::Intel) {
if !nested {
// Disable nested virtualization for Intel
entry.ecx &= !(1 << VMX_ECX_BIT);
}
break;
}
break;
}
if entry.function == 0x8000_0001 {
if !nested {
@@ -859,10 +982,9 @@ pub fn configure_vcpu(
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
if !kvm_hyperv && let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
// Need to check that the TSC doesn't vary with dynamic frequency
#[allow(unused_unsafe)]
// SAFETY: cpuid called with valid leaves
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx & (1u32 << INVARIANT_TSC_EDX_BIT)
> 0
{
if unsafe { x86_64::__cpuid(0x8000_0007) }.edx & (1u32 << INVARIANT_TSC_EDX_BIT) > 0 {
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x4000_0000, None, CpuidReg::EAX, 0x4000_0010);
cpuid.retain(|c| c.function != 0x4000_0010);
cpuid.push(CpuIdEntry {
@@ -888,17 +1010,19 @@ pub fn configure_vcpu(
regs::setup_msrs(vcpu).map_err(Error::MsrsConfiguration)?;
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
regs::setup_regs(vcpu, kernel_entry_point).map_err(Error::RegsConfiguration)?;
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
if setup_registers {
regs::setup_regs(vcpu, kernel_entry_point).map_err(Error::RegsConfiguration)?;
// CPUs are required (by Intel sdm spec) to boot in x2apic mode if any
// of the apic IDs is larger than 255. Experimentally, the Linux kernel
// does not recognize the last vCPU if x2apic is not enabled when
// there are 256 vCPUs in a flat hierarchy (i.e. max x2apic ID is 255),
// so we need to enable x2apic in this case as well.
let enable_x2_apic_mode = get_max_x2apic_id(topology) > MAX_SUPPORTED_CPUS_LEGACY;
regs::setup_sregs(&guest_memory.memory(), vcpu, enable_x2_apic_mode)
.map_err(Error::SregsConfiguration)?;
// CPUs are required (by Intel sdm spec) to boot in x2apic mode if any
// of the apic IDs is larger than 255. Experimentally, the Linux kernel
// does not recognize the last vCPU if x2apic is not enabled when
// there are 256 vCPUs in a flat hierarchy (i.e. max x2apic ID is 255),
// so we need to enable x2apic in this case as well.
let enable_x2_apic_mode = get_max_x2apic_id(topology) > MAX_SUPPORTED_CPUS_LEGACY;
regs::setup_sregs(&guest_memory.memory(), vcpu, enable_x2_apic_mode)
.map_err(Error::SregsConfiguration)?;
}
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
}
interrupts::set_lint(vcpu).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
Ok(())
@@ -942,7 +1066,7 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
/// * `cmdline_addr` - Address in `guest_mem` where the kernel command line was loaded.
/// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator.
/// * `num_cpus` - Number of virtual CPUs the guest will have.
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
pub fn configure_system(
guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress,
@@ -951,9 +1075,7 @@ pub fn configure_system(
_num_cpus: u32,
setup_header: Option<setup_header>,
rsdp_addr: Option<GuestAddress>,
serial_number: Option<&str>,
uuid: Option<&str>,
oem_strings: Option<&[&str]>,
smbios: Option<&SmbiosConfig>,
topology: Option<(u16, u16, u16, u16)>,
) -> super::Result<()> {
// Write EBDA address to location where ACPICA expects to find it
@@ -961,8 +1083,7 @@ pub fn configure_system(
.write_obj((layout::EBDA_START.0 >> 4) as u16, layout::EBDA_POINTER)
.map_err(Error::EbdaSetup)?;
let size = smbios::setup_smbios(guest_mem, serial_number, uuid, oem_strings)
.map_err(Error::SmbiosSetup)?;
let size = smbios::setup_smbios(guest_mem, smbios).map_err(Error::SmbiosSetup)?;
// Place the MP table after the SMIOS table aligned to 16 bytes
let offset = GuestAddress(layout::SMBIOS_START).unchecked_add(size);
@@ -1032,8 +1153,9 @@ pub fn generate_ram_ranges(guest_mem: &GuestMemoryMmap) -> super::Result<Vec<Ram
// Generate the first usable physical memory range before the gap. The e820 map
// should only report memory above 1MiB.
let first_ram_range = {
let (first_region_start, first_region_end) =
ram_regions.first().ok_or(super::Error::MemmapTableSetup)?;
let (first_region_start, first_region_end) = ram_regions
.first()
.ok_or(super::Error::MemmapTableGeneration)?;
let high_ram_start = layout::HIGH_RAM_START.raw_value();
let mem_32bit_reserved_start = layout::MEM_32BIT_RESERVED_START.raw_value();
@@ -1046,7 +1168,7 @@ pub fn generate_ram_ranges(guest_mem: &GuestMemoryMmap) -> super::Result<Vec<Ram
high_ram_start: 0x{high_ram_start:08x}, mem_32bit_reserved_start: 0x{mem_32bit_reserved_start:08x}"
);
return Err(super::Error::MemmapTableSetup);
return Err(super::Error::MemmapTableGeneration);
}
info!(
@@ -1149,7 +1271,7 @@ fn configure_pvh(
guest_mem
.checked_offset(
memmap_start_addr,
mem::size_of::<hvm_memmap_table_entry>() * start_info.memmap_entries as usize,
size_of::<hvm_memmap_table_entry>() * start_info.memmap_entries as usize,
)
.ok_or(super::Error::MemmapTablePastRamEnd)?;
@@ -1157,9 +1279,9 @@ fn configure_pvh(
for memmap_entry in memmap {
guest_mem
.write_obj(memmap_entry, memmap_start_addr)
.map_err(|_| super::Error::MemmapTableSetup)?;
.map_err(super::Error::MemmapTableSetup)?;
memmap_start_addr =
memmap_start_addr.unchecked_add(mem::size_of::<hvm_memmap_table_entry>() as u64);
memmap_start_addr.unchecked_add(size_of::<hvm_memmap_table_entry>() as u64);
}
// The hvm_start_info struct itself must be stored at PVH_START_INFO
@@ -1168,13 +1290,13 @@ fn configure_pvh(
let start_info_addr = layout::PVH_INFO_START;
guest_mem
.checked_offset(start_info_addr, mem::size_of::<hvm_start_info>())
.checked_offset(start_info_addr, size_of::<hvm_start_info>())
.ok_or(super::Error::StartInfoPastRamEnd)?;
// Write the start_info struct to guest memory.
guest_mem
.write_obj(start_info, start_info_addr)
.map_err(|_| super::Error::StartInfoSetup)?;
.map_err(super::Error::StartInfoSetup)?;
Ok(())
}
@@ -1247,7 +1369,7 @@ fn configure_32bit_entry(
let zero_page_addr = layout::ZERO_PAGE_START;
guest_mem
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
.checked_offset(zero_page_addr, size_of::<boot_params>())
.ok_or(super::Error::ZeroPagePastRamEnd)?;
guest_mem
.write_obj(params, zero_page_addr)
@@ -1305,26 +1427,15 @@ pub fn initramfs_load_addr(
Ok(aligned_addr)
}
pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
pub fn get_host_cpu_phys_bits(_hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
// SAFETY: call cpuid with valid leaves
#[allow(unused_unsafe)]
unsafe {
let leaf = x86_64::__cpuid(0x8000_0000);
// Detect and handle AMD SME (Secure Memory Encryption) properly.
// Some physical address bits may become reserved when the feature is enabled.
// See AMD64 Architecture Programmer's Manual Volume 2, Section 7.10.1
let reduced = if leaf.eax >= 0x8000_001f
&& matches!(hypervisor.get_cpu_vendor(), CpuVendor::AMD)
&& x86_64::__cpuid(0x8000_001f).eax & 0x1 != 0
{
(x86_64::__cpuid(0x8000_001f).ebx >> 6) & 0x3f
} else {
0
};
if leaf.eax >= 0x8000_0008 {
let leaf = x86_64::__cpuid(0x8000_0008);
((leaf.eax & 0xff) - reduced) as u8
(leaf.eax & 0xff) as u8
} else {
36
}
@@ -1506,8 +1617,6 @@ mod unit_tests {
Some(layout::RSDP_POINTER),
None,
None,
None,
None,
);
config_err.unwrap_err();
@@ -1530,8 +1639,6 @@ mod unit_tests {
None,
None,
None,
None,
None,
)
.unwrap();
@@ -1559,8 +1666,6 @@ mod unit_tests {
None,
None,
None,
None,
None,
)
.unwrap();
@@ -1574,8 +1679,6 @@ mod unit_tests {
None,
None,
None,
None,
None,
)
.unwrap();
}

View File

@@ -3,35 +3,36 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
#![allow(non_camel_case_types)]
use std::os::raw;
use vm_memory::ByteValued;
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
pub const MP_BUS: ::std::os::raw::c_uint = 1;
pub const MP_IOAPIC: ::std::os::raw::c_uint = 2;
pub const MP_INTSRC: ::std::os::raw::c_uint = 3;
pub const MP_LINTSRC: ::std::os::raw::c_uint = 4;
pub const CPU_ENABLED: ::std::os::raw::c_uint = 1;
pub const CPU_BOOTPROCESSOR: ::std::os::raw::c_uint = 2;
pub const MPC_APIC_USABLE: ::std::os::raw::c_uint = 1;
pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
pub const MP_PROCESSOR: raw::c_uint = 0;
pub const MP_BUS: raw::c_uint = 1;
pub const MP_IOAPIC: raw::c_uint = 2;
pub const MP_INTSRC: raw::c_uint = 3;
pub const MP_LINTSRC: raw::c_uint = 4;
pub const CPU_ENABLED: raw::c_uint = 1;
pub const CPU_BOOTPROCESSOR: raw::c_uint = 2;
pub const MPC_APIC_USABLE: raw::c_uint = 1;
pub const MP_IRQDIR_DEFAULT: raw::c_uint = 0;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpf_intel {
pub signature: [::std::os::raw::c_uchar; 4usize],
pub physptr: ::std::os::raw::c_uint,
pub length: ::std::os::raw::c_uchar,
pub specification: ::std::os::raw::c_uchar,
pub checksum: ::std::os::raw::c_uchar,
pub feature1: ::std::os::raw::c_uchar,
pub feature2: ::std::os::raw::c_uchar,
pub feature3: ::std::os::raw::c_uchar,
pub feature4: ::std::os::raw::c_uchar,
pub feature5: ::std::os::raw::c_uchar,
pub signature: [raw::c_uchar; 4usize],
pub physptr: raw::c_uint,
pub length: raw::c_uchar,
pub specification: raw::c_uchar,
pub checksum: raw::c_uchar,
pub feature1: raw::c_uchar,
pub feature2: raw::c_uchar,
pub feature3: raw::c_uchar,
pub feature4: raw::c_uchar,
pub feature5: raw::c_uchar,
}
const _: () = assert!(::core::mem::size_of::<mpf_intel>() == 16);
const _: () = assert!(size_of::<mpf_intel>() == 16);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
@@ -41,24 +42,24 @@ unsafe impl ByteValued for mpf_intel {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_table {
pub signature: [::std::os::raw::c_uchar; 4usize],
pub length: ::std::os::raw::c_ushort,
pub spec: ::std::os::raw::c_uchar,
pub checksum: ::std::os::raw::c_uchar,
pub oem: [::std::os::raw::c_uchar; 8usize],
pub productid: [::std::os::raw::c_uchar; 12usize],
pub oemptr: ::std::os::raw::c_uint,
pub oemsize: ::std::os::raw::c_ushort,
pub oemcount: ::std::os::raw::c_ushort,
pub lapic: ::std::os::raw::c_uint,
pub reserved: ::std::os::raw::c_uint,
pub signature: [raw::c_uchar; 4usize],
pub length: raw::c_ushort,
pub spec: raw::c_uchar,
pub checksum: raw::c_uchar,
pub oem: [raw::c_uchar; 8usize],
pub productid: [raw::c_uchar; 12usize],
pub oemptr: raw::c_uint,
pub oemsize: raw::c_ushort,
pub oemcount: raw::c_ushort,
pub lapic: raw::c_uint,
pub reserved: raw::c_uint,
}
const _: () = {
assert!(::core::mem::size_of::<mpc_table>() == 4 + 2 + 1 + 1 + 8 + 12 + 4 + 2 + 2 + 4 + 4);
assert!(::core::mem::size_of::<::std::os::raw::c_uint>() == 4);
assert!(::core::mem::size_of::<::std::os::raw::c_ushort>() == 2);
assert!(::core::mem::size_of::<::std::os::raw::c_uchar>() == 1);
assert!(size_of::<mpc_table>() == 4 + 2 + 1 + 1 + 8 + 12 + 4 + 2 + 2 + 4 + 4);
assert!(size_of::<raw::c_uint>() == 4);
assert!(size_of::<raw::c_ushort>() == 2);
assert!(size_of::<raw::c_uchar>() == 1);
};
// SAFETY: all members of this struct are plain integers
@@ -70,16 +71,16 @@ unsafe impl ByteValued for mpc_table {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_cpu {
pub type_: ::std::os::raw::c_uchar,
pub apicid: ::std::os::raw::c_uchar,
pub apicver: ::std::os::raw::c_uchar,
pub cpuflag: ::std::os::raw::c_uchar,
pub cpufeature: ::std::os::raw::c_uint,
pub featureflag: ::std::os::raw::c_uint,
pub reserved: [::std::os::raw::c_uint; 2usize],
pub type_: raw::c_uchar,
pub apicid: raw::c_uchar,
pub apicver: raw::c_uchar,
pub cpuflag: raw::c_uchar,
pub cpufeature: raw::c_uint,
pub featureflag: raw::c_uint,
pub reserved: [raw::c_uint; 2usize],
}
const _: () = assert!(::core::mem::size_of::<mpc_cpu>() == 20);
const _: () = assert!(size_of::<mpc_cpu>() == 20);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
@@ -89,12 +90,12 @@ unsafe impl ByteValued for mpc_cpu {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_bus {
pub type_: ::std::os::raw::c_uchar,
pub busid: ::std::os::raw::c_uchar,
pub bustype: [::std::os::raw::c_uchar; 6usize],
pub type_: raw::c_uchar,
pub busid: raw::c_uchar,
pub bustype: [raw::c_uchar; 6usize],
}
const _: () = assert!(::core::mem::size_of::<mpc_bus>() == 8);
const _: () = assert!(size_of::<mpc_bus>() == 8);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
@@ -104,14 +105,14 @@ unsafe impl ByteValued for mpc_bus {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_ioapic {
pub type_: ::std::os::raw::c_uchar,
pub apicid: ::std::os::raw::c_uchar,
pub apicver: ::std::os::raw::c_uchar,
pub flags: ::std::os::raw::c_uchar,
pub apicaddr: ::std::os::raw::c_uint,
pub type_: raw::c_uchar,
pub apicid: raw::c_uchar,
pub apicver: raw::c_uchar,
pub flags: raw::c_uchar,
pub apicaddr: raw::c_uint,
}
const _: () = assert!(::core::mem::size_of::<mpc_ioapic>() == 8);
const _: () = assert!(size_of::<mpc_ioapic>() == 8);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
@@ -121,39 +122,39 @@ unsafe impl ByteValued for mpc_ioapic {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_intsrc {
pub type_: ::std::os::raw::c_uchar,
pub irqtype: ::std::os::raw::c_uchar,
pub irqflag: ::std::os::raw::c_ushort,
pub srcbus: ::std::os::raw::c_uchar,
pub srcbusirq: ::std::os::raw::c_uchar,
pub dstapic: ::std::os::raw::c_uchar,
pub dstirq: ::std::os::raw::c_uchar,
pub type_: raw::c_uchar,
pub irqtype: raw::c_uchar,
pub irqflag: raw::c_ushort,
pub srcbus: raw::c_uchar,
pub srcbusirq: raw::c_uchar,
pub dstapic: raw::c_uchar,
pub dstirq: raw::c_uchar,
}
const _: () = assert!(::core::mem::size_of::<mpc_intsrc>() == 8);
const _: () = assert!(size_of::<mpc_intsrc>() == 8);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
// would be nowhere for them to exist.
unsafe impl ByteValued for mpc_intsrc {}
pub const MP_IRQ_SOURCE_TYPES_MP_INT: ::std::os::raw::c_uint = 0;
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: ::std::os::raw::c_uint = 1;
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: ::std::os::raw::c_uint = 3;
pub const MP_IRQ_SOURCE_TYPES_MP_INT: raw::c_uint = 0;
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: raw::c_uint = 1;
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: raw::c_uint = 3;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_lintsrc {
pub type_: ::std::os::raw::c_uchar,
pub irqtype: ::std::os::raw::c_uchar,
pub irqflag: ::std::os::raw::c_ushort,
pub srcbusid: ::std::os::raw::c_uchar,
pub srcbusirq: ::std::os::raw::c_uchar,
pub destapic: ::std::os::raw::c_uchar,
pub destapiclint: ::std::os::raw::c_uchar,
pub type_: raw::c_uchar,
pub irqtype: raw::c_uchar,
pub irqflag: raw::c_ushort,
pub srcbusid: raw::c_uchar,
pub srcbusirq: raw::c_uchar,
pub destapic: raw::c_uchar,
pub destapiclint: raw::c_uchar,
}
const _: () = assert!(::core::mem::size_of::<mpc_lintsrc>() == 8);
const _: () = assert!(size_of::<mpc_lintsrc>() == 8);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
@@ -163,14 +164,14 @@ unsafe impl ByteValued for mpc_lintsrc {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_oemtable {
pub signature: [::std::os::raw::c_uchar; 4usize],
pub length: ::std::os::raw::c_ushort,
pub rev: ::std::os::raw::c_uchar,
pub checksum: ::std::os::raw::c_uchar,
pub mpc: [::std::os::raw::c_uchar; 8usize],
pub signature: [raw::c_uchar; 4usize],
pub length: raw::c_ushort,
pub rev: raw::c_uchar,
pub checksum: raw::c_uchar,
pub mpc: [raw::c_uchar; 8usize],
}
const _: () = assert!(::core::mem::size_of::<mpc_oemtable>() == 16);
const _: () = assert!(size_of::<mpc_oemtable>() == 16);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there

View File

@@ -5,7 +5,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use std::{mem, result, slice};
use std::result;
use libc::c_uchar;
use log::{info, warn};
@@ -101,10 +101,8 @@ const CPU_FEATURE_APIC: u32 = 0x200;
const CPU_FEATURE_FPU: u32 = 0x001;
fn compute_checksum<T: Copy + ByteValued>(v: &T) -> u8 {
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
let mut checksum: u8 = 0;
for i in v_slice.iter() {
for i in v.as_slice().iter() {
checksum = checksum.wrapping_add(*i);
}
checksum
@@ -116,13 +114,13 @@ fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 {
}
fn compute_mp_size(num_cpus: u32) -> usize {
mem::size_of::<MpfIntelWrapper>()
+ mem::size_of::<MpcTableWrapper>()
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
+ mem::size_of::<MpcIoapicWrapper>()
+ mem::size_of::<MpcBusWrapper>()
+ mem::size_of::<MpcIntsrcWrapper>() * 16
+ mem::size_of::<MpcLintsrcWrapper>() * 2
size_of::<MpfIntelWrapper>()
+ size_of::<MpcTableWrapper>()
+ size_of::<MpcCpuWrapper>() * (num_cpus as usize)
+ size_of::<MpcIoapicWrapper>()
+ size_of::<MpcBusWrapper>()
+ size_of::<MpcIntsrcWrapper>() * 16
+ size_of::<MpcLintsrcWrapper>() * 2
}
/// Performs setup of the MP table for the given `num_cpus`.
@@ -169,7 +167,7 @@ pub fn setup_mptable(
{
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
let size = mem::size_of::<MpfIntelWrapper>() as u64;
let size = size_of::<MpfIntelWrapper>() as u64;
mpf_intel.0.signature = *SMP_MAGIC_IDENT;
mpf_intel.0.length = 1;
mpf_intel.0.specification = 4;
@@ -183,10 +181,10 @@ pub fn setup_mptable(
// We set the location of the mpc_table here but we can't fill it out until we have the length
// of the entire table later.
let table_base = base_mp;
base_mp = base_mp.unchecked_add(mem::size_of::<MpcTableWrapper>() as u64);
base_mp = base_mp.unchecked_add(size_of::<MpcTableWrapper>() as u64);
{
let size = mem::size_of::<MpcCpuWrapper>();
let size = size_of::<MpcCpuWrapper>();
for cpu_id in 0..num_cpus {
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
@@ -207,7 +205,7 @@ pub fn setup_mptable(
}
}
{
let size = mem::size_of::<MpcBusWrapper>();
let size = size_of::<MpcBusWrapper>();
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
mpc_bus.0.busid = 0;
@@ -218,7 +216,7 @@ pub fn setup_mptable(
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0));
}
{
let size = mem::size_of::<MpcIoapicWrapper>();
let size = size_of::<MpcIoapicWrapper>();
let mut mpc_ioapic = MpcIoapicWrapper(mpspec::mpc_ioapic::default());
mpc_ioapic.0.type_ = mpspec::MP_IOAPIC as u8;
mpc_ioapic.0.apicid = ioapicid;
@@ -232,7 +230,7 @@ pub fn setup_mptable(
}
// Per kvm_setup_default_irq_routing() in kernel
for i in 0..16 {
let size = mem::size_of::<MpcIntsrcWrapper>();
let size = size_of::<MpcIntsrcWrapper>();
let mut mpc_intsrc = MpcIntsrcWrapper(mpspec::mpc_intsrc::default());
mpc_intsrc.0.type_ = mpspec::MP_INTSRC as u8;
mpc_intsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_INT as u8;
@@ -247,7 +245,7 @@ pub fn setup_mptable(
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0));
}
{
let size = mem::size_of::<MpcLintsrcWrapper>();
let size = size_of::<MpcLintsrcWrapper>();
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_EXT_INT as u8;
@@ -262,7 +260,7 @@ pub fn setup_mptable(
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
}
{
let size = mem::size_of::<MpcLintsrcWrapper>();
let size = size_of::<MpcLintsrcWrapper>();
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_NMI as u8;
@@ -307,11 +305,11 @@ mod unit_tests {
fn table_entry_size(type_: u8) -> usize {
match type_ as u32 {
mpspec::MP_PROCESSOR => mem::size_of::<MpcCpuWrapper>(),
mpspec::MP_BUS => mem::size_of::<MpcBusWrapper>(),
mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
mpspec::MP_PROCESSOR => size_of::<MpcCpuWrapper>(),
mpspec::MP_BUS => size_of::<MpcBusWrapper>(),
mpspec::MP_IOAPIC => size_of::<MpcIoapicWrapper>(),
mpspec::MP_INTSRC => size_of::<MpcIntsrcWrapper>(),
mpspec::MP_LINTSRC => size_of::<MpcLintsrcWrapper>(),
_ => panic!("unrecognized mpc table entry type: {type_}"),
}
}
@@ -404,7 +402,7 @@ mod unit_tests {
.unwrap();
let mut entry_offset = mpc_offset
.checked_add(mem::size_of::<MpcTableWrapper>() as GuestUsize)
.checked_add(size_of::<MpcTableWrapper>() as GuestUsize)
.unwrap();
let mut cpu_count = 0;
while entry_offset < mpc_end {

View File

@@ -6,7 +6,7 @@
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use std::{mem, result};
use std::result;
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
use hypervisor::arch::x86::regs::CR0_PE;
@@ -134,7 +134,7 @@ fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
let boot_gdt_addr = BOOT_GDT_START;
for (index, entry) in table.iter().enumerate() {
let addr = guest_mem
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
.checked_offset(boot_gdt_addr, index * size_of::<u64>())
.ok_or(Error::CheckGdtAddr)?;
guest_mem.write_obj(*entry, addr).map_err(Error::WriteGdt)?;
}
@@ -170,11 +170,11 @@ pub fn configure_segments_and_sregs(
// Write segments
write_gdt_table(&gdt_table[..], mem)?;
sregs.gdt.base = BOOT_GDT_START.raw_value();
sregs.gdt.limit = mem::size_of_val(&gdt_table) as u16 - 1;
sregs.gdt.limit = size_of_val(&gdt_table) as u16 - 1;
write_idt_value(0, mem)?;
sregs.idt.base = BOOT_IDT_START.raw_value();
sregs.idt.limit = mem::size_of::<u64>() as u16 - 1;
sregs.idt.limit = size_of::<u64>() as u16 - 1;
sregs.cs = code_seg;
sregs.ds = data_seg;

View File

@@ -6,7 +6,7 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::{mem, result, slice};
use std::result;
use thiserror::Error;
use uuid::Uuid;
@@ -28,38 +28,74 @@ pub enum Error {
Clear,
/// Failure to write SMBIOS entrypoint structure
#[error("Failure to write SMBIOS entrypoint structure")]
WriteSmbiosEp,
WriteSmbiosEp(#[source] vm_memory::GuestMemoryError),
/// Failure to write additional data to memory
#[error("Failure to write additional data to memory")]
WriteData,
WriteData(#[source] vm_memory::GuestMemoryError),
/// Failure to parse uuid, uuid format may be error
#[error("Failure to parse uuid")]
ParseUuid(#[source] uuid::Error),
#[error("Failure to parse uuid: {1}")]
ParseUuid(#[source] uuid::Error, String),
/// SMBIOS string index overflow (u8 limit reached).
#[error("SMBIOS string index overflow (u8 limit reached: {})", u8::MAX)]
TooManyStrings,
}
pub type Result<T> = result::Result<T, Error>;
// Constants sourced from SMBIOS Spec 3.2.0.
// Constants sourced from SMBIOS Spec 3.9.0.
const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
const BIOS_INFORMATION: u8 = 0;
const SYSTEM_INFORMATION: u8 = 1;
const OEM_STRINGS: u8 = 11;
const SYSTEM_ENCLOSURE: u8 = 3;
const END_OF_TABLE: u8 = 127;
const SYSTEM_WAKE_UP_TYPE_UNKNOWN: u8 = 0x02;
const CHASSIS_TYPE_UNKNOWN: u8 = 0x02;
const CHASSIS_STATE_UNKNOWN: u8 = 0x02;
const CHASSIS_SECURITY_STATUS_NONE: u8 = 0x03;
const PCI_SUPPORTED: u64 = 1 << 7;
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
pub const DEFAULT_SYSTEM_MANUFACTURER: &str = "Cloud Hypervisor";
pub const DEFAULT_SYSTEM_PRODUCT_NAME: &str = "cloud-hypervisor";
fn compute_checksum<T: Copy>(v: &T) -> u8 {
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SmbiosConfig {
pub system: Option<SmbiosSystem>,
pub chassis: Option<SmbiosChassisConfig>,
pub oem_strings: Box<[String]>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SmbiosSystem {
pub manufacturer: Option<String>,
pub product_name: Option<String>,
pub version: Option<String>,
pub serial_number: Option<String>,
pub uuid: Option<String>,
pub sku_number: Option<String>,
pub family: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SmbiosChassisConfig {
pub asset_tag: Option<String>,
}
impl SmbiosConfig {
pub fn is_empty(&self) -> bool {
*self == Self::default()
}
}
fn compute_checksum<T: Copy + ByteValued>(v: &T) -> u8 {
let mut checksum: u8 = 0;
for i in v_slice.iter() {
for i in v.as_slice().iter() {
checksum = checksum.wrapping_add(*i);
}
(!checksum).wrapping_add(1)
}
#[repr(C)]
#[repr(packed)]
#[repr(C, packed)]
#[derive(Default, Copy, Clone)]
struct Smbios30Entrypoint {
signature: [u8; 5usize],
@@ -74,8 +110,7 @@ struct Smbios30Entrypoint {
physptr: u64,
}
#[repr(C)]
#[repr(packed)]
#[repr(C, packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosBiosInfo {
r#type: u8,
@@ -91,8 +126,7 @@ struct SmbiosBiosInfo {
characteristics_ext2: u8,
}
#[repr(C)]
#[repr(packed)]
#[repr(C, packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosSysInfo {
r#type: u8,
@@ -108,8 +142,7 @@ struct SmbiosSysInfo {
family: u8,
}
#[repr(C)]
#[repr(packed)]
#[repr(C, packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosOemStrings {
r#type: u8,
@@ -118,8 +151,34 @@ struct SmbiosOemStrings {
count: u8,
}
#[repr(C)]
#[repr(packed)]
/// SMBIOS Chassis Table (Type 3) as defined in DMTF SMBIOS 3.9.0:
/// https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.9.0.pdf
/// Note: trailing fields are omitted, so this structure is not complete.
#[repr(C, packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosChassis {
r#type: u8,
length: u8,
handle: u16,
manufacturer: u8,
chassis_type: u8,
version: u8,
serial_number: u8,
asset_tag: u8,
bootup_state: u8,
power_supply_state: u8,
thermal_state: u8,
security_status: u8,
oem_defined: u32,
height: u8,
number_of_power_cords: u8,
contained_element_count: u8,
contained_element_record_length: u8,
// followed by contained element records (optional, variable-length)
// followed by sku_number: u8, rack_type: u8, rack_height: u8
}
#[repr(C, packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosEndOfTable {
r#type: u8,
@@ -136,6 +195,8 @@ unsafe impl ByteValued for SmbiosSysInfo {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for SmbiosOemStrings {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for SmbiosChassis {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for SmbiosEndOfTable {}
fn write_and_incr<T: ByteValued>(
@@ -143,9 +204,9 @@ fn write_and_incr<T: ByteValued>(
val: T,
mut curptr: GuestAddress,
) -> Result<GuestAddress> {
mem.write_obj(val, curptr).map_err(|_| Error::WriteData)?;
mem.write_obj(val, curptr).map_err(Error::WriteData)?;
curptr = curptr
.checked_add(mem::size_of::<T>() as u64)
.checked_add(size_of::<T>() as u64)
.ok_or(Error::NotEnoughMemory)?;
Ok(curptr)
}
@@ -162,14 +223,155 @@ fn write_string(
Ok(curptr)
}
pub fn setup_smbios(
fn write_opt_string(
mem: &GuestMemoryMmap,
serial_number: Option<&str>,
uuid: Option<&str>,
oem_strings: Option<&[&str]>,
) -> Result<u64> {
s: Option<&str>,
cur: GuestAddress,
) -> Result<GuestAddress> {
if let Some(v) = s {
write_string(mem, v, cur)
} else {
Ok(cur)
}
}
fn write_string_terminator(
mem: &GuestMemoryMmap,
cur: GuestAddress,
has_strings: bool,
) -> Result<GuestAddress> {
// SMBIOS DSP0134 §6.1.3: if all string-reference fields are 0, follow the
// formatted section with two null bytes (empty string-set).
if has_strings {
write_and_incr(mem, 0u8, cur)
} else {
let cur = write_and_incr(mem, 0u8, cur)?;
write_and_incr(mem, 0u8, cur)
}
}
/// Allocate the next string index for an SMBIOS string-set.
///
/// Per SMBIOS DSP0134, index `0` means "no string", so valid indices run from
/// `1` to `255`. Returns `0` when `present` is `false`. Otherwise returns the
/// current value of `*next` and advances it by one. Fails with
/// [`Error::TooManyStrings`] once all 255 indices have been used: `next`
/// starts at `1`, so it can only be `0` here after wrapping past `255`.
fn alloc_index(next: &mut u8, present: bool) -> Result<u8> {
if !present {
return Ok(0);
}
let idx = *next;
if idx == 0 {
return Err(Error::TooManyStrings);
}
*next = next.wrapping_add(1);
Ok(idx)
}
fn write_type1_system(
mem: &GuestMemoryMmap,
curptr: &mut GuestAddress,
handle: &mut u16,
system: Option<&SmbiosSystem>,
) -> Result<()> {
*handle += 1;
let manufacturer = system
.and_then(|s| s.manufacturer.as_deref())
.unwrap_or(DEFAULT_SYSTEM_MANUFACTURER);
let product = system
.and_then(|s| s.product_name.as_deref())
.unwrap_or(DEFAULT_SYSTEM_PRODUCT_NAME);
let version = system.and_then(|s| s.version.as_deref());
let serial = system.and_then(|s| s.serial_number.as_deref());
let uuid = system.and_then(|s| s.uuid.as_deref());
let sku = system.and_then(|s| s.sku_number.as_deref());
let family = system.and_then(|s| s.family.as_deref());
let uuid_number = uuid
.map(Uuid::parse_str)
.transpose()
.map_err(|e| Error::ParseUuid(e, uuid.unwrap().to_string()))?
.unwrap_or(Uuid::nil());
let mut next = 1u8;
let manufacturer_idx = alloc_index(&mut next, true)?;
let product_idx = alloc_index(&mut next, true)?;
let version_idx = alloc_index(&mut next, version.is_some())?;
let serial_idx = alloc_index(&mut next, serial.is_some())?;
let sku_idx = alloc_index(&mut next, sku.is_some())?;
let family_idx = alloc_index(&mut next, family.is_some())?;
let sys = SmbiosSysInfo {
r#type: SYSTEM_INFORMATION,
length: size_of::<SmbiosSysInfo>() as u8,
handle: *handle,
manufacturer: manufacturer_idx,
product_name: product_idx,
version: version_idx,
serial_number: serial_idx,
uuid: uuid_number.to_bytes_le(),
wake_up_type: SYSTEM_WAKE_UP_TYPE_UNKNOWN,
sku: sku_idx,
family: family_idx,
};
*curptr = write_and_incr(mem, sys, *curptr)?;
*curptr = write_string(mem, manufacturer, *curptr)?;
*curptr = write_string(mem, product, *curptr)?;
*curptr = write_opt_string(mem, version, *curptr)?;
*curptr = write_opt_string(mem, serial, *curptr)?;
*curptr = write_opt_string(mem, sku, *curptr)?;
*curptr = write_opt_string(mem, family, *curptr)?;
*curptr = write_and_incr(mem, 0u8, *curptr)?;
Ok(())
}
fn write_type3_chassis(
mem: &GuestMemoryMmap,
curptr: &mut GuestAddress,
handle: &mut u16,
chassis: &SmbiosChassisConfig,
) -> Result<()> {
*handle += 1;
let asset_tag = chassis.asset_tag.as_deref();
let mut next = 1u8;
let asset_idx = alloc_index(&mut next, asset_tag.is_some())?;
let ch = SmbiosChassis {
r#type: SYSTEM_ENCLOSURE,
length: size_of::<SmbiosChassis>() as u8,
handle: *handle,
manufacturer: 0,
chassis_type: CHASSIS_TYPE_UNKNOWN,
version: 0,
serial_number: 0,
asset_tag: asset_idx,
bootup_state: CHASSIS_STATE_UNKNOWN,
power_supply_state: CHASSIS_STATE_UNKNOWN,
thermal_state: CHASSIS_STATE_UNKNOWN,
security_status: CHASSIS_SECURITY_STATUS_NONE,
contained_element_count: 0,
contained_element_record_length: 0,
..Default::default()
};
*curptr = write_and_incr(mem, ch, *curptr)?;
*curptr = write_opt_string(mem, asset_tag, *curptr)?;
*curptr = write_string_terminator(mem, *curptr, asset_tag.is_some())?;
Ok(())
}
pub fn setup_smbios(mem: &GuestMemoryMmap, smbios: Option<&SmbiosConfig>) -> Result<u64> {
let system = smbios.and_then(|cfg| cfg.system.as_ref());
let chassis = smbios.and_then(|cfg| cfg.chassis.as_ref());
let oem_strings: &[String] = smbios.map_or(&[], |cfg| &cfg.oem_strings);
let physptr = GuestAddress(SMBIOS_START)
.checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
.checked_add(size_of::<Smbios30Entrypoint>() as u64)
.ok_or(Error::NotEnoughMemory)?;
let mut curptr = physptr;
let mut handle = 0;
@@ -178,7 +380,7 @@ pub fn setup_smbios(
handle += 1;
let smbios_biosinfo = SmbiosBiosInfo {
r#type: BIOS_INFORMATION,
length: mem::size_of::<SmbiosBiosInfo>() as u8,
length: size_of::<SmbiosBiosInfo>() as u8,
handle,
vendor: 1, // First string written in this section
version: 2, // Second string written in this section
@@ -192,39 +394,18 @@ pub fn setup_smbios(
curptr = write_and_incr(mem, 0u8, curptr)?;
}
{
handle += 1;
write_type1_system(mem, &mut curptr, &mut handle, system)?;
let uuid_number = uuid
.map(Uuid::parse_str)
.transpose()
.map_err(Error::ParseUuid)?
.unwrap_or(Uuid::nil());
let smbios_sysinfo = SmbiosSysInfo {
r#type: SYSTEM_INFORMATION,
length: mem::size_of::<SmbiosSysInfo>() as u8,
handle,
manufacturer: 1, // First string written in this section
product_name: 2, // Second string written in this section
serial_number: serial_number.map(|_| 3).unwrap_or_default(), // 3rd string
uuid: uuid_number.to_bytes_le(), // set uuid
..Default::default()
};
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
curptr = write_string(mem, "Cloud Hypervisor", curptr)?;
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
if let Some(serial_number) = serial_number {
curptr = write_string(mem, serial_number, curptr)?;
}
curptr = write_and_incr(mem, 0u8, curptr)?;
if let Some(chassis) = chassis {
write_type3_chassis(mem, &mut curptr, &mut handle, chassis)?;
}
if let Some(oem_strings) = oem_strings {
if !oem_strings.is_empty() {
handle += 1;
let smbios_oemstrings = SmbiosOemStrings {
r#type: OEM_STRINGS,
length: mem::size_of::<SmbiosOemStrings>() as u8,
length: size_of::<SmbiosOemStrings>() as u8,
handle,
count: oem_strings.len() as u8,
};
@@ -235,14 +416,14 @@ pub fn setup_smbios(
curptr = write_string(mem, s, curptr)?;
}
curptr = write_and_incr(mem, 0u8, curptr)?;
curptr = write_string_terminator(mem, curptr, true)?;
}
{
handle += 1;
let smbios_end = SmbiosEndOfTable {
r#type: END_OF_TABLE,
length: mem::size_of::<SmbiosEndOfTable>() as u8,
length: size_of::<SmbiosEndOfTable>() as u8,
handle,
};
curptr = write_and_incr(mem, smbios_end, curptr)?;
@@ -253,7 +434,7 @@ pub fn setup_smbios(
{
let mut smbios_ep = Smbios30Entrypoint {
signature: *SM3_MAGIC_IDENT,
length: mem::size_of::<Smbios30Entrypoint>() as u8,
length: size_of::<Smbios30Entrypoint>() as u8,
// SMBIOS rev 3.2.0
majorver: 0x03,
minorver: 0x02,
@@ -265,43 +446,261 @@ pub fn setup_smbios(
};
smbios_ep.checksum = compute_checksum(&smbios_ep);
mem.write_obj(smbios_ep, GuestAddress(SMBIOS_START))
.map_err(|_| Error::WriteSmbiosEp)?;
.map_err(Error::WriteSmbiosEp)?;
}
Ok(curptr.unchecked_offset_from(physptr) + std::mem::size_of::<Smbios30Entrypoint>() as u64)
Ok(curptr.unchecked_offset_from(physptr) + size_of::<Smbios30Entrypoint>() as u64)
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn struct_size() {
assert_eq!(
mem::size_of::<Smbios30Entrypoint>(),
0x18usize,
concat!("Size of: ", stringify!(Smbios30Entrypoint))
);
assert_eq!(
mem::size_of::<SmbiosBiosInfo>(),
0x14usize,
concat!("Size of: ", stringify!(SmbiosBiosInfo))
);
assert_eq!(
mem::size_of::<SmbiosSysInfo>(),
0x1busize,
concat!("Size of: ", stringify!(SmbiosSysInfo))
);
/// Collects all strings after a SMBIOS structure, stopping at the double-NUL terminator and returns next addr.
fn read_string_set(mem: &GuestMemoryMmap, addr: GuestAddress) -> (Vec<String>, GuestAddress) {
let mut cur = addr;
let read_byte = |addr: GuestAddress| -> u8 { mem.read_obj(addr).unwrap() };
// SMBIOS string-set: NUL-terminated strings, terminated by an extra NUL.
// Empty string-set is exactly "\0\0".
if read_byte(cur) == 0 {
let next = cur.checked_add(1).unwrap();
assert_eq!(read_byte(next), 0);
return (Vec::new(), next.checked_add(1).unwrap());
}
let mut strings = Vec::new();
loop {
let mut bytes = Vec::new();
loop {
let b = read_byte(cur);
cur = cur.checked_add(1).unwrap();
if b == 0 {
break;
}
bytes.push(b);
}
strings.push(String::from_utf8(bytes).unwrap());
// If the next byte is NUL, that's the extra terminator.
if read_byte(cur) == 0 {
cur = cur.checked_add(1).unwrap();
break;
}
}
(strings, cur)
}
#[test]
fn entrypoint_checksum() {
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
setup_smbios(&mem, None, None, None).unwrap();
setup_smbios(&mem, None).unwrap();
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
assert_eq!(compute_checksum(&smbios_ep), 0);
}
#[test]
fn entrypoint_struct_size() {
assert_eq!(
size_of::<Smbios30Entrypoint>(),
0x18usize,
concat!("Size of: ", stringify!(Smbios30Entrypoint))
);
assert_eq!(
size_of::<SmbiosBiosInfo>(),
0x14usize,
concat!("Size of: ", stringify!(SmbiosBiosInfo))
);
assert_eq!(
size_of::<SmbiosSysInfo>(),
0x1busize,
concat!("Size of: ", stringify!(SmbiosSysInfo))
);
}
#[test]
fn smbios_chassis_empty_string_set_has_double_null() {
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
let smbios = SmbiosConfig {
chassis: Some(SmbiosChassisConfig::default()),
..Default::default()
};
setup_smbios(&mem, Some(&smbios)).unwrap();
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
let mut cur = GuestAddress(smbios_ep.physptr);
let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap();
cur = cur.checked_add(bios.length as u64).unwrap();
let (_, next) = read_string_set(&mem, cur);
cur = next;
let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap();
cur = cur.checked_add(sys.length as u64).unwrap();
let (_, next) = read_string_set(&mem, cur);
cur = next;
let chassis: SmbiosChassis = mem.read_obj(cur).unwrap();
cur = cur.checked_add(chassis.length as u64).unwrap();
// SMBIOS DSP0134 §6.1.3: empty string-set ends with double NUL.
let b0: u8 = mem.read_obj(cur).unwrap();
let b1: u8 = mem.read_obj(cur.checked_add(1).unwrap()).unwrap();
assert_eq!(b0, 0);
assert_eq!(b1, 0);
cur = cur.checked_add(2).unwrap();
let end: SmbiosEndOfTable = mem.read_obj(cur).unwrap();
assert_eq!(end.r#type, END_OF_TABLE);
}
#[test]
fn smbios_chassis_oem_strings_layout() {
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
let smbios = SmbiosConfig {
chassis: Some(SmbiosChassisConfig {
asset_tag: Some("rack1".to_string()),
}),
oem_strings: ["o1".to_string(), "o2".to_string()].into(),
..Default::default()
};
setup_smbios(&mem, Some(&smbios)).unwrap();
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
let mut cur = GuestAddress(smbios_ep.physptr);
let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap();
cur = cur.checked_add(bios.length as u64).unwrap();
let (_, next) = read_string_set(&mem, cur);
cur = next;
let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap();
cur = cur.checked_add(sys.length as u64).unwrap();
let (_, next) = read_string_set(&mem, cur);
cur = next;
let chassis: SmbiosChassis = mem.read_obj(cur).unwrap();
assert_eq!(chassis.r#type, SYSTEM_ENCLOSURE);
assert_eq!(chassis.asset_tag, 1);
cur = cur.checked_add(chassis.length as u64).unwrap();
let (chassis_strings, next) = read_string_set(&mem, cur);
assert_eq!(chassis_strings, vec!["rack1"]);
cur = next;
let oem: SmbiosOemStrings = mem.read_obj(cur).unwrap();
assert_eq!(oem.r#type, OEM_STRINGS);
assert_eq!(oem.count, 2);
cur = cur.checked_add(oem.length as u64).unwrap();
let (oem_strings, next) = read_string_set(&mem, cur);
assert_eq!(oem_strings, vec!["o1", "o2"]);
cur = next;
let end: SmbiosEndOfTable = mem.read_obj(cur).unwrap();
assert_eq!(end.r#type, END_OF_TABLE);
}
#[test]
fn smbios_strings_terminators_default() {
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
setup_smbios(&mem, None).unwrap();
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
let mut cur = GuestAddress(smbios_ep.physptr);
let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap();
assert_eq!(bios.r#type, BIOS_INFORMATION);
cur = cur.checked_add(bios.length as u64).unwrap();
let (bios_strings, next) = read_string_set(&mem, cur);
assert_eq!(bios_strings, vec!["cloud-hypervisor", "0"]);
cur = next;
let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap();
assert_eq!(sys.r#type, SYSTEM_INFORMATION);
assert_eq!(sys.manufacturer, 1);
assert_eq!(sys.product_name, 2);
assert_eq!(sys.version, 0);
assert_eq!(sys.serial_number, 0);
assert_eq!(sys.sku, 0);
assert_eq!(sys.family, 0);
cur = cur.checked_add(sys.length as u64).unwrap();
let (sys_strings, next) = read_string_set(&mem, cur);
assert_eq!(
sys_strings,
vec![DEFAULT_SYSTEM_MANUFACTURER, DEFAULT_SYSTEM_PRODUCT_NAME]
);
cur = next;
let end: SmbiosEndOfTable = mem.read_obj(cur).unwrap();
assert_eq!(end.r#type, END_OF_TABLE);
}
#[test]
fn smbios_strings_too_many() {
let mut next = 1u8;
for _ in 0..255 {
alloc_index(&mut next, true).unwrap();
}
let err = alloc_index(&mut next, true).unwrap_err();
assert!(matches!(err, Error::TooManyStrings));
}
#[test]
fn smbios_uuid_invalid_rejected() {
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
let smbios = SmbiosConfig {
system: Some(SmbiosSystem {
uuid: Some("not-a-uuid".to_string()),
..Default::default()
}),
..Default::default()
};
let err = setup_smbios(&mem, Some(&smbios)).unwrap_err();
assert!(matches!(err, Error::ParseUuid(_, _)));
}
#[test]
fn smbios_uuid_written_le() {
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
let uuid_str = "00112233-4455-6677-8899-aabbccddeeff";
let smbios = SmbiosConfig {
system: Some(SmbiosSystem {
uuid: Some(uuid_str.to_string()),
..Default::default()
}),
..Default::default()
};
setup_smbios(&mem, Some(&smbios)).unwrap();
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
let mut cur = GuestAddress(smbios_ep.physptr);
let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap();
cur = cur.checked_add(bios.length as u64).unwrap();
let (_, next) = read_string_set(&mem, cur);
cur = next;
let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap();
assert_eq!(sys.uuid, Uuid::parse_str(uuid_str).unwrap().to_bytes_le());
}
#[test]
fn smbios_write_fails_with_too_small_memory() {
let mem = GuestMemoryMmap::from_ranges(&[(
GuestAddress(SMBIOS_START),
size_of::<Smbios30Entrypoint>(),
)])
.unwrap();
let err = setup_smbios(&mem, None).unwrap_err();
assert!(matches!(err, Error::WriteData(_)));
}
}

View File

@@ -2,7 +2,8 @@
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::io::{self, Read, Seek, SeekFrom};
use std::slice;
use std::str::FromStr;
use log::{debug, info};
@@ -15,11 +16,11 @@ use crate::GuestMemoryMmap;
#[derive(Error, Debug)]
pub enum TdvfError {
#[error("Failed read TDVF descriptor")]
ReadDescriptor(#[source] std::io::Error),
ReadDescriptor(#[source] io::Error),
#[error("Failed read TDVF descriptor offset")]
ReadDescriptorOffset(#[source] std::io::Error),
ReadDescriptorOffset(#[source] io::Error),
#[error("Failed read GUID table")]
ReadGuidTable(#[source] std::io::Error),
ReadGuidTable(#[source] io::Error),
#[error("Invalid descriptor signature")]
InvalidDescriptorSignature,
#[error("Invalid descriptor size")]
@@ -162,10 +163,7 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
let mut descriptor: TdvfDescriptor = Default::default();
// SAFETY: we read exactly the size of the descriptor header
file.read_exact(unsafe {
std::slice::from_raw_parts_mut(
&mut descriptor as *mut _ as *mut u8,
std::mem::size_of::<TdvfDescriptor>(),
)
slice::from_raw_parts_mut((&raw mut descriptor).cast(), size_of::<TdvfDescriptor>())
})
.map_err(TdvfError::ReadDescriptor)?;
@@ -174,8 +172,7 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
}
if descriptor.length as usize
!= std::mem::size_of::<TdvfDescriptor>()
+ std::mem::size_of::<TdvfSection>() * descriptor.num_sections as usize
!= size_of::<TdvfDescriptor>() + size_of::<TdvfSection>() * descriptor.num_sections as usize
{
return Err(TdvfError::InvalidDescriptorSize);
}
@@ -189,9 +186,9 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
// SAFETY: we read exactly the advertised sections
file.read_exact(unsafe {
std::slice::from_raw_parts_mut(
sections.as_mut_ptr() as *mut u8,
descriptor.num_sections as usize * std::mem::size_of::<TdvfSection>(),
slice::from_raw_parts_mut(
sections.as_mut_ptr().cast(),
descriptor.num_sections as usize * size_of::<TdvfSection>(),
)
})
.map_err(TdvfError::ReadDescriptor)?;
@@ -305,7 +302,7 @@ fn align_hob(v: u64) -> u64 {
impl TdHob {
fn update_offset<T>(&mut self) {
self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64);
self.current_offset = align_hob(self.current_offset + size_of::<T>() as u64);
}
pub fn start(offset: u64) -> TdHob {
@@ -322,7 +319,7 @@ impl TdHob {
// Write end
let end = HobHeader {
r#type: HobType::EndOfHobList,
length: std::mem::size_of::<HobHeader>() as u16,
length: size_of::<HobHeader>() as u16,
reserved: 0,
};
info!("Writing HOB end {:x} {:x?}", self.current_offset, end);
@@ -335,7 +332,7 @@ impl TdHob {
let handoff = HobHandoffInfoTable {
header: HobHeader {
r#type: HobType::Handoff,
length: std::mem::size_of::<HobHandoffInfoTable>() as u16,
length: size_of::<HobHandoffInfoTable>() as u16,
reserved: 0,
},
version: 0x9,
@@ -362,7 +359,7 @@ impl TdHob {
let resource_descriptor = HobResourceDescriptor {
header: HobHeader {
r#type: HobType::ResourceDescriptor,
length: std::mem::size_of::<HobResourceDescriptor>() as u16,
length: size_of::<HobResourceDescriptor>() as u16,
reserved: 0,
},
owner: EfiGuid::default(),
@@ -439,8 +436,7 @@ impl TdHob {
// We already know the HobGuidType size is 8 bytes multiple, but we
// need the total size to be 8 bytes multiple. That is why the ACPI
// table size must be 8 bytes multiple as well.
let length = std::mem::size_of::<HobGuidType>() as u16
+ align_hob(table_content.len() as u64) as u16;
let length = size_of::<HobGuidType>() as u16 + align_hob(table_content.len() as u64) as u16;
let hob_guid_type = HobGuidType {
header: HobHeader {
r#type: HobType::GuidExtension,
@@ -462,7 +458,7 @@ impl TdHob {
);
mem.write_obj(hob_guid_type, GuestAddress(self.current_offset))
.map_err(TdvfError::GuestMemoryWriteHob)?;
let current_offset = self.current_offset + std::mem::size_of::<HobGuidType>() as u64;
let current_offset = self.current_offset + size_of::<HobGuidType>() as u64;
// In case the table is quite large, let's make sure we can handle
// retrying until everything has been correctly copied.
@@ -493,7 +489,7 @@ impl TdHob {
guid_type: HobGuidType {
header: HobHeader {
r#type: HobType::GuidExtension,
length: std::mem::size_of::<TdPayload>() as u16,
length: size_of::<TdPayload>() as u16,
reserved: 0,
},
// HOB_PAYLOAD_INFO_GUID
@@ -526,7 +522,7 @@ mod unit_tests {
#[test]
#[ignore]
fn test_parse_tdvf_sections() {
let mut f = std::fs::File::open("tdvf.fd").unwrap();
let mut f = File::open("tdvf.fd").unwrap();
let (sections, _) = parse_tdvf_sections(&mut f).unwrap();
for section in sections {
eprintln!("{section:x?}");

View File

@@ -2,22 +2,25 @@
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
edition.workspace = true
name = "block"
rust-version.workspace = true
version = "0.1.0"
[features]
default = []
io_uring = ["dep:io-uring"]
test-utils = []
[dependencies]
bitflags = { workspace = true }
byteorder = { workspace = true }
crc-any = "2.5.0"
flate2 = "1.0"
io-uring = { version = "0.7.11", optional = true }
crc-any = "3.0.0"
flate2 = "1.1"
io-uring = { version = "0.7.12", optional = true }
libc = { workspace = true }
log = { workspace = true }
remain = "0.2.15"
serde = { workspace = true, features = ["derive"] }
smallvec = "1.15.1"
smallvec = { workspace = true }
thiserror = { workspace = true }
uuid = { workspace = true, features = ["v4"] }
virtio-bindings = { workspace = true }
@@ -29,7 +32,11 @@ vm-memory = { workspace = true, features = [
] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = { workspace = true }
zerocopy = { workspace = true, features = ["derive"] }
zstd = "0.13"
[dev-dependencies]
cfg-if = { workspace = true }
[lints]
workspace = true

269
block/src/aligned_buffer.rs Normal file
View File

@@ -0,0 +1,269 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use std::alloc::{Layout, alloc_zeroed, dealloc};
use std::os::unix::fs::FileExt;
use std::{io, slice};
/// RAII aligned heap buffer for O_DIRECT I/O.
///
/// Handles the alignment math for offset and length, allocating a buffer
/// that satisfies O_DIRECT constraints. The caller's logical data lives
/// at `as_slice()`/`as_mut_slice()` (accounting for head padding when the
/// requested offset is not alignment-aligned). The full aligned region is
/// used internally for pread/pwrite via `FileExt`.
pub(crate) struct AlignedBuffer {
ptr: *mut u8,
layout: Layout,
head_pad: usize,
user_len: usize,
aligned_len: usize,
aligned_offset: u64,
}
impl AlignedBuffer {
/// Create a new aligned buffer for I/O at `offset` of `len` bytes with
/// the given `alignment` requirement.
///
/// When offset and length are already aligned, `head_pad == 0` and the
/// full buffer equals the user's logical portion (no overhead).
pub fn new(offset: u64, len: usize, alignment: usize) -> io::Result<Self> {
if alignment == 0 || !alignment.is_power_of_two() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"alignment must be a non-zero power of two",
));
}
let mask = alignment as u64 - 1;
let aligned_offset = offset & !mask;
let head_pad = (offset - aligned_offset) as usize;
let min_len = head_pad
.checked_add(len)
.ok_or_else(|| io::Error::other("aligned buffer length overflow"))?;
let aligned_len = if min_len == 0 {
0
} else {
let remainder = min_len % alignment;
if remainder == 0 {
min_len
} else {
min_len
.checked_add(alignment - remainder)
.ok_or_else(|| io::Error::other("aligned buffer length overflow"))?
}
};
// alloc_zeroed is UB on a zero-sized layout, so round the allocation
// up to one alignment unit for the zero-length case. The padding is
// never exposed: as_slice/full_slice report aligned_len/user_len (0).
let layout = Layout::from_size_align(aligned_len.max(alignment), alignment)
.map_err(|e| io::Error::other(format!("invalid aligned layout: {e}")))?;
// SAFETY: layout has non-zero size.
let ptr = unsafe { alloc_zeroed(layout) };
if ptr.is_null() {
return Err(io::Error::new(
io::ErrorKind::OutOfMemory,
"aligned allocation failed",
));
}
Ok(AlignedBuffer {
ptr,
layout,
head_pad,
user_len: len,
aligned_len,
aligned_offset,
})
}
/// The caller's logical portion of the buffer (read-only).
pub fn as_slice(&self) -> &[u8] {
// SAFETY: ptr is valid for layout.size() bytes; head_pad + user_len <= layout.size().
unsafe { slice::from_raw_parts(self.ptr.add(self.head_pad), self.user_len) }
}
/// The caller's logical portion of the buffer (mutable).
pub fn as_mut_slice(&mut self) -> &mut [u8] {
// SAFETY: ptr is valid for layout.size() bytes; head_pad + user_len <= layout.size().
unsafe { slice::from_raw_parts_mut(self.ptr.add(self.head_pad), self.user_len) }
}
fn full_slice(&self) -> &[u8] {
// SAFETY: ptr is valid for layout.size() bytes; aligned_len <= layout.size().
unsafe { slice::from_raw_parts(self.ptr, self.aligned_len) }
}
fn full_mut_slice(&mut self) -> &mut [u8] {
// SAFETY: ptr is valid for layout.size() bytes; aligned_len <= layout.size().
unsafe { slice::from_raw_parts_mut(self.ptr, self.aligned_len) }
}
/// Read into the buffer from `f`, tolerating a short read at EOF.
///
/// Returns the number of caller-logical bytes now valid in `as_slice()`,
/// accounting for head padding and any short read.
pub fn read_from(&mut self, f: &impl FileExt) -> io::Result<usize> {
let mut total = 0usize;
while total < self.aligned_len {
let offset = self
.aligned_offset
.checked_add(total as u64)
.ok_or_else(|| io::Error::other("aligned buffer offset overflow"))?;
match f.read_at(&mut self.full_mut_slice()[total..], offset) {
Ok(0) => break,
Ok(n) => total += n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(total.saturating_sub(self.head_pad).min(self.user_len))
}
/// Write the full aligned region from this buffer to `f`.
pub fn write_to(&self, f: &impl FileExt) -> io::Result<()> {
f.write_all_at(self.full_slice(), self.aligned_offset)
}
}
impl Drop for AlignedBuffer {
fn drop(&mut self) {
// SAFETY: ptr was allocated by alloc_zeroed with self.layout.
unsafe { dealloc(self.ptr, self.layout) };
}
}
// SAFETY: The buffer is a plain heap allocation with no interior references.
unsafe impl Send for AlignedBuffer {}
#[cfg(test)]
mod tests {
use std::io::Write;
use std::os::unix::fs::FileExt;
use vmm_sys_util::tempfile::TempFile;
use super::*;
fn create_pattern_file(size: usize) -> TempFile {
let tf = TempFile::new().unwrap();
let pattern: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
tf.as_file().write_all(&pattern).unwrap();
tf.as_file().sync_all().unwrap();
tf
}
#[test]
fn test_read_aligned() {
let size = 4096usize;
let tf = create_pattern_file(size);
let alignment = 512;
let mut abuf = AlignedBuffer::new(0, size, alignment).unwrap();
abuf.read_from(tf.as_file()).unwrap();
let expected: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
assert_eq!(abuf.as_slice(), &expected[..]);
}
#[test]
fn test_zero_len_is_noop() {
let tf = create_pattern_file(512);
let mut abuf = AlignedBuffer::new(100, 0, 512).unwrap();
abuf.read_from(tf.as_file()).unwrap();
abuf.write_to(tf.as_file()).unwrap();
assert!(abuf.as_slice().is_empty());
assert!(abuf.as_mut_slice().is_empty());
}
#[test]
fn test_read_unaligned_offset() {
let file_size = 8192usize;
let tf = create_pattern_file(file_size);
let alignment = 512;
let offset = 100u64;
let len = 200usize;
let mut abuf = AlignedBuffer::new(offset, len, alignment).unwrap();
abuf.read_from(tf.as_file()).unwrap();
let expected: Vec<u8> = (offset as usize..offset as usize + len)
.map(|i| (i % 251) as u8)
.collect();
assert_eq!(abuf.as_slice(), &expected[..]);
}
#[test]
fn test_write_aligned() {
let size = 4096usize;
let tf = create_pattern_file(size);
let alignment = 512;
let data: Vec<u8> = (0..size).map(|i| ((i + 1) % 251) as u8).collect();
let mut abuf = AlignedBuffer::new(0, size, alignment).unwrap();
abuf.as_mut_slice().copy_from_slice(&data);
abuf.write_to(tf.as_file()).unwrap();
let mut readback = vec![0u8; size];
tf.as_file().read_exact_at(&mut readback, 0).unwrap();
assert_eq!(readback, data);
}
#[test]
fn test_write_unaligned_offset_rmw() {
let file_size = 8192usize;
let tf = create_pattern_file(file_size);
let alignment = 512;
let offset = 100u64;
let len = 200usize;
let data: Vec<u8> = (0..len).map(|i| ((i + 1) % 239) as u8).collect();
let mut abuf = AlignedBuffer::new(offset, len, alignment).unwrap();
abuf.read_from(tf.as_file()).unwrap();
abuf.as_mut_slice().copy_from_slice(&data);
abuf.write_to(tf.as_file()).unwrap();
let mut whole = vec![0u8; file_size];
tf.as_file().read_exact_at(&mut whole, 0).unwrap();
let before: Vec<u8> = (0..offset as usize).map(|i| (i % 251) as u8).collect();
assert_eq!(&whole[..offset as usize], &before[..]);
assert_eq!(&whole[offset as usize..offset as usize + len], &data[..]);
let after_start = offset as usize + len;
let after: Vec<u8> = (after_start..file_size).map(|i| (i % 251) as u8).collect();
assert_eq!(&whole[after_start..], &after[..]);
}
#[test]
fn test_4096_alignment() {
let file_size = 16384usize;
let tf = create_pattern_file(file_size);
let alignment = 4096;
let offset = 4096u64;
let len = 4096usize;
let data: Vec<u8> = (0..len).map(|i| ((i + 1) % 239) as u8).collect();
let mut abuf = AlignedBuffer::new(offset, len, alignment).unwrap();
abuf.read_from(tf.as_file()).unwrap();
abuf.as_mut_slice().copy_from_slice(&data);
abuf.write_to(tf.as_file()).unwrap();
let mut abuf = AlignedBuffer::new(offset, len, alignment).unwrap();
abuf.read_from(tf.as_file()).unwrap();
assert_eq!(abuf.as_slice(), &data[..]);
let mut whole = vec![0u8; file_size];
tf.as_file().read_exact_at(&mut whole, 0).unwrap();
let before: Vec<u8> = (0..offset as usize).map(|i| (i % 251) as u8).collect();
assert_eq!(&whole[..offset as usize], &before[..]);
let after_start = offset as usize + len;
let after: Vec<u8> = (after_start..file_size).map(|i| (i % 251) as u8).collect();
assert_eq!(&whole[after_start..], &after[..]);
}
}

402
block/src/aligned_file.rs Normal file
View File

@@ -0,0 +1,402 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::{File, Metadata};
use std::io;
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::fs::FileExt;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::file_traits::FileSync;
use vmm_sys_util::seek_hole::SeekHole;
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
use crate::aligned_buffer::AlignedBuffer;
use crate::{SECTOR_SIZE, probe_direct_alignment};
/// True when `buf_ptr`/`len`/`offset` already satisfy `alignment`
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
fn is_aligned(alignment: usize, buf_ptr: usize, len: usize, offset: u64) -> bool {
alignment == 0
|| (buf_ptr.is_multiple_of(alignment)
&& len.is_multiple_of(alignment)
&& offset.is_multiple_of(alignment as u64))
}
/// A `File` that transparently satisfies O_DIRECT alignment requirements.
///
/// `alignment == 0` means no O_DIRECT (all I/O passes straight through).
/// For unaligned requests under O_DIRECT, I/O is bounced through an
/// `AlignedBuffer` (read-modify-write for writes).
#[derive(Debug)]
pub struct AlignedFile {
file: File,
alignment: usize,
}
impl AlignedFile {
/// Wrap `file`, querying the O_DIRECT block alignment when `direct_io`.
pub fn new(file: File, direct_io: bool) -> Self {
let alignment = if direct_io {
probe_direct_alignment(file.as_raw_fd()).unwrap_or(SECTOR_SIZE) as usize
} else {
0
};
AlignedFile { file, alignment }
}
pub fn alignment(&self) -> usize {
self.alignment
}
pub fn file(&self) -> &File {
&self.file
}
pub fn file_mut(&mut self) -> &mut File {
&mut self.file
}
pub fn try_clone(&self) -> io::Result<Self> {
Ok(AlignedFile {
file: self.file.try_clone()?,
alignment: self.alignment,
})
}
pub fn set_len(&self, size: u64) -> io::Result<()> {
self.file.set_len(size)
}
pub fn metadata(&self) -> io::Result<Metadata> {
self.file.metadata()
}
pub fn sync_all(&self) -> io::Result<()> {
self.file.sync_all()
}
pub fn sync_data(&self) -> io::Result<()> {
self.file.sync_data()
}
pub fn is_direct(&self) -> bool {
self.alignment != 0
}
pub fn is_writable(&self) -> bool {
// SAFETY: fcntl with F_GETFL is safe and doesn't modify the file descriptor
let flags = unsafe { libc::fcntl(self.file.as_raw_fd(), libc::F_GETFL) };
if flags < 0 {
return false;
}
let access_mode = flags & libc::O_ACCMODE;
access_mode == libc::O_WRONLY || access_mode == libc::O_RDWR
}
/// Wrap `file` with an explicit alignment, bypassing the probe. Used by
/// tests to force the bounce/RMW path without a real O_DIRECT fd.
#[cfg(test)]
pub fn with_alignment(file: File, alignment: usize) -> Self {
AlignedFile { file, alignment }
}
/// Read `len` bytes at `offset` through an aligned bounce buffer.
pub(crate) fn read_unaligned(
&self,
offset: u64,
len: usize,
scatter: impl FnOnce(&[u8]) -> io::Result<()>,
) -> io::Result<usize> {
let mut abuf = AlignedBuffer::new(offset, len, self.alignment)?;
let n = abuf.read_from(&self.file)?;
scatter(&abuf.as_slice()[..n])?;
Ok(n)
}
/// Write `len` bytes at `offset` through an aligned bounce buffer.
pub(crate) fn write_unaligned(
&self,
offset: u64,
len: usize,
gather: impl FnOnce(&mut [u8]) -> io::Result<()>,
) -> io::Result<usize> {
let mut abuf = AlignedBuffer::new(offset, len, self.alignment)?;
abuf.read_from(&self.file)?; // RMW: preserve head/tail padding
gather(abuf.as_mut_slice())?;
abuf.write_to(&self.file)?;
Ok(len)
}
}
impl FileExt for AlignedFile {
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
if is_aligned(self.alignment, buf.as_ptr() as usize, buf.len(), offset) {
return self.file.read_at(buf, offset);
}
self.read_unaligned(offset, buf.len(), |data| {
buf[..data.len()].copy_from_slice(data);
Ok(())
})
}
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
if is_aligned(self.alignment, buf.as_ptr() as usize, buf.len(), offset) {
return self.file.write_at(buf, offset);
}
self.write_unaligned(offset, buf.len(), |dst| {
dst.copy_from_slice(buf);
Ok(())
})
}
}
impl WriteZeroesAt for AlignedFile {
fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
self.file.write_zeroes_at(offset, length)
}
}
impl PunchHole for AlignedFile {
fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
self.file.punch_hole(offset, length)
}
}
impl FileSync for AlignedFile {
fn fsync(&mut self) -> io::Result<()> {
self.file.fsync()
}
}
impl SeekHole for AlignedFile {
fn seek_hole(&mut self, offset: u64) -> io::Result<Option<u64>> {
self.file.seek_hole(offset)
}
fn seek_data(&mut self, offset: u64) -> io::Result<Option<u64>> {
self.file.seek_data(offset)
}
}
impl Clone for AlignedFile {
fn clone(&self) -> Self {
self.try_clone().expect("AlignedFile cloning failed")
}
}
impl AsRawFd for AlignedFile {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl AsFd for AlignedFile {
fn as_fd(&self) -> BorrowedFd<'_> {
self.file.as_fd()
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use std::os::unix::fs::FileExt;
use vmm_sys_util::tempfile::TempFile;
use super::*;
fn pattern_file(size: usize) -> TempFile {
let tf = TempFile::new().unwrap();
let p: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
tf.as_file().write_all(&p).unwrap();
tf.as_file().sync_all().unwrap();
tf
}
fn forced(file: File, alignment: usize) -> AlignedFile {
AlignedFile { file, alignment }
}
#[test]
fn new_probes_alignment_and_accessors() {
let tf = pattern_file(8192);
// Not O_DIRECT, so new() falls back to SECTOR_SIZE (512).
let mut af = AlignedFile::new(tf.as_file().try_clone().unwrap(), true);
assert_eq!(af.alignment(), 512);
let _ = af.file();
let _ = af.file_mut();
let _ = af.try_clone().unwrap();
let plain = AlignedFile::new(tf.as_file().try_clone().unwrap(), false);
assert_eq!(plain.alignment(), 0);
}
#[test]
fn read_unaligned_offset_matches_contents() {
let tf = pattern_file(8192);
let af = forced(tf.as_file().try_clone().unwrap(), 512);
let mut buf = vec![0u8; 200];
assert_eq!(af.read_at(&mut buf, 100).unwrap(), 200);
let want: Vec<u8> = (100..300).map(|i| (i % 251) as u8).collect();
assert_eq!(buf, want);
}
#[test]
fn read_unaligned_short_at_eof() {
let tf = pattern_file(100);
let af = forced(tf.as_file().try_clone().unwrap(), 512);
let mut buf = vec![0u8; 200];
assert_eq!(af.read_at(&mut buf, 10).unwrap(), 90);
}
#[test]
fn write_unaligned_offset_is_rmw() {
let tf = pattern_file(8192);
let af = forced(tf.as_file().try_clone().unwrap(), 512);
let data: Vec<u8> = (0..200).map(|i| ((i + 1) % 239) as u8).collect();
assert_eq!(af.write_at(&data, 100).unwrap(), 200);
let mut whole = vec![0u8; 8192];
tf.as_file().read_exact_at(&mut whole, 0).unwrap();
let before: Vec<u8> = (0..100).map(|i| (i % 251) as u8).collect();
assert_eq!(&whole[..100], &before[..]);
assert_eq!(&whole[100..300], &data[..]);
let after: Vec<u8> = (300..8192).map(|i| (i % 251) as u8).collect();
assert_eq!(&whole[300..], &after[..]);
}
#[test]
fn aligned_passthrough_roundtrip() {
let tf = pattern_file(4096);
let af = forced(tf.as_file().try_clone().unwrap(), 512);
let mut buf = vec![0u8; 512];
assert_eq!(af.read_at(&mut buf, 512).unwrap(), 512);
let want: Vec<u8> = (512..1024).map(|i| (i % 251) as u8).collect();
assert_eq!(buf, want);
}
#[test]
fn no_alignment_is_plain_passthrough() {
let tf = pattern_file(100);
let af = forced(tf.as_file().try_clone().unwrap(), 0);
let mut buf = vec![0u8; 50];
assert_eq!(af.read_at(&mut buf, 10).unwrap(), 50);
}
#[test]
fn test_unaligned_read_beyond_eof_returns_zero() {
let tf = pattern_file(100);
let af = forced(tf.as_file().try_clone().unwrap(), 512);
let mut buf = vec![0u8; 16];
assert_eq!(af.read_at(&mut buf, 200).unwrap(), 0);
}
#[test]
fn test_unaligned_write_extends_at_eof() {
let file_size = 100usize;
let tf = pattern_file(file_size);
let af = forced(tf.as_file().try_clone().unwrap(), 512);
let data = b"xyz";
assert_eq!(af.write_at(data, file_size as u64).unwrap(), data.len());
let mut readback = vec![0u8; file_size + data.len()];
tf.as_file().read_exact_at(&mut readback, 0).unwrap();
let expected_prefix: Vec<u8> = (0..file_size).map(|i| (i % 251) as u8).collect();
assert_eq!(&readback[..file_size], &expected_prefix[..]);
assert_eq!(&readback[file_size..], data);
}
#[test]
fn test_empty_unaligned_io_is_noop() {
let tf = pattern_file(100);
let af = forced(tf.as_file().try_clone().unwrap(), 512);
let mut read_buf = [];
assert_eq!(af.read_at(&mut read_buf, 1).unwrap(), 0);
assert_eq!(af.write_at(&[], 1).unwrap(), 0);
}
#[test]
fn read_unaligned_scatters_in_a_single_copy() {
let file = pattern_file(8192);
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
let mut out = vec![0u8; 200];
let n = aligned_file
.read_unaligned(100, 200, |data| {
out.copy_from_slice(data);
Ok(())
})
.unwrap();
assert_eq!(n, 200);
let want: Vec<u8> = (100..300).map(|i| (i % 251) as u8).collect();
assert_eq!(out, want);
}
#[test]
fn read_unaligned_closure_short_at_eof() {
let file = pattern_file(100);
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
let mut seen = 0usize;
let n = aligned_file
.read_unaligned(10, 200, |data| {
seen = data.len();
Ok(())
})
.unwrap();
assert_eq!(n, 90);
assert_eq!(seen, 90);
}
#[test]
fn write_unaligned_gather_is_rmw() {
let file = pattern_file(8192);
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
let data: Vec<u8> = (0..200).map(|i| ((i + 1) % 239) as u8).collect();
let n = aligned_file
.write_unaligned(100, 200, |buf| {
buf.copy_from_slice(&data);
Ok(())
})
.unwrap();
assert_eq!(n, 200);
let mut whole = vec![0u8; 8192];
file.as_file().read_exact_at(&mut whole, 0).unwrap();
let before: Vec<u8> = (0..100).map(|i| (i % 251) as u8).collect();
assert_eq!(&whole[..100], &before[..]);
assert_eq!(&whole[100..300], &data[..]);
let after: Vec<u8> = (300..8192).map(|i| (i % 251) as u8).collect();
assert_eq!(&whole[300..], &after[..]);
}
#[test]
fn read_unaligned_propagates_closure_error() {
let file = pattern_file(8192);
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
let err = aligned_file
.read_unaligned(100, 200, |_| {
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom"))
})
.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
fn write_unaligned_propagates_closure_error() {
let file = pattern_file(8192);
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
let err = aligned_file
.write_unaligned(100, 200, |_| {
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom"))
})
.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
}

View File

@@ -1,128 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::marker::PhantomData;
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use thiserror::Error;
use vmm_sys_util::eventfd::EventFd;
use crate::{BatchRequest, DiskTopology};
#[derive(Error, Debug)]
pub enum DiskFileError {
/// Failed getting disk file size.
#[error("Failed getting disk file size")]
Size(#[source] std::io::Error),
/// Failed creating a new AsyncIo.
#[error("Failed creating a new AsyncIo")]
NewAsyncIo(#[source] std::io::Error),
/// Unsupported operation.
#[error("Unsupported operation")]
Unsupported,
/// Resize failed
#[error("Resize failed")]
ResizeError(#[source] std::io::Error),
}
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
/// A wrapper for [`RawFd`] capturing the lifetime of a corresponding [`DiskFile`].
///
/// This fulfills the same role as [`BorrowedFd`] but is tailored to the limitations
/// by some implementations of [`DiskFile`], which wrap the effective [`File`]
/// in an `Arc<Mutex<T>>`, making the use of [`BorrowedFd`] impossible.
///
/// [`BorrowedFd`]: std::os::fd::BorrowedFd
#[derive(Copy, Clone, Debug)]
pub struct BorrowedDiskFd<'fd> {
raw_fd: RawFd,
_lifetime: PhantomData<&'fd OwnedFd>,
}
impl BorrowedDiskFd<'_> {
pub(super) fn new(raw_fd: RawFd) -> Self {
Self {
raw_fd,
_lifetime: PhantomData,
}
}
}
impl AsRawFd for BorrowedDiskFd<'_> {
fn as_raw_fd(&self) -> RawFd {
self.raw_fd
}
}
/// Abstraction over the effective [`File`] backing up a block device,
/// with support for synchronous and asynchronous I/O.
///
/// This allows abstracting over raw image formats as well as structured
/// image formats.
pub trait DiskFile: Send {
/// Returns the logical disk size a guest will see.
///
/// For raw formats, this is equal to [`Self::physical_size`]. For file formats
/// that wrap disk images in a container (e.g. QCOW2), this refers to the
/// effective size that the guest will see.
fn logical_size(&mut self) -> DiskFileResult<u64>;
/// Returns the physical size of the underlying file.
fn physical_size(&mut self) -> DiskFileResult<u64>;
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
fn topology(&mut self) -> DiskTopology {
DiskTopology::default()
}
fn resize(&mut self, _size: u64) -> DiskFileResult<()> {
Err(DiskFileError::Unsupported)
}
/// Returns the file descriptor of the underlying disk image file.
///
/// The file descriptor is supposed to be used for `fcntl()` calls but no
/// other operation.
fn fd(&mut self) -> BorrowedDiskFd<'_>;
}
#[derive(Error, Debug)]
pub enum AsyncIoError {
/// Failed vectored reading from file.
#[error("Failed vectored reading from file")]
ReadVectored(#[source] std::io::Error),
/// Failed vectored writing to file.
#[error("Failed vectored writing to file")]
WriteVectored(#[source] std::io::Error),
/// Failed synchronizing file.
#[error("Failed synchronizing file")]
Fsync(#[source] std::io::Error),
/// Failed submitting batch requests.
#[error("Failed submitting batch requests")]
SubmitBatchRequests(#[source] std::io::Error),
}
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
pub trait AsyncIo: Send {
fn notifier(&self) -> &EventFd;
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()>;
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()>;
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
fn batch_requests_enabled(&self) -> bool {
false
}
fn submit_batch_requests(&mut self, _batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
Ok(())
}
}

158
block/src/disk_file.rs Normal file
View File

@@ -0,0 +1,158 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
//! Composable disk capability traits for the block crate.
//!
//! Small traits define individual capabilities:
//!
//! - [`DiskSize`] - reported capacity (logical size)
//! - [`PhysicalSize`] - host allocation size
//! - [`DiskFd`] - backing file descriptor access
//! - [`Geometry`] - sector/cluster geometry (default 512B)
//! - [`SparseCapable`] - sparse and zero flag support
//! - [`Resizable`] - online resize
//!
//! [`DiskFile`] is a supertrait that bundles the universal capabilities
//! (`DiskSize` + `Geometry`). [`FullDiskFile`] adds all optional
//! capabilities. [`AsyncDiskFile`] extends `DiskFile` with async I/O
//! construction for virtio queue workers. [`AsyncFullDiskFile`]
//! combines both axes.
//!
//! ```text
//! DiskFile: DiskSize + Geometry + Sync
//! / \
//! FullDiskFile: AsyncDiskFile:
//! DiskFile + PhysicalSize + DiskFile + Unpin
//! DiskFd + SparseCapable + try_clone, create_async_io
//! Resizable
//! \ /
//! AsyncFullDiskFile: FullDiskFile + AsyncDiskFile
//! ```
//!
//! Readonly accessors take `&self`. Only [`Resizable::resize`] requires
//! `&mut self`. Errors are returned as [`BlockResult`].
use std::fmt::Debug;
use crate::async_io::{AsyncIo, BorrowedDiskFd};
use crate::{BlockResult, DiskTopology};
/// Reported capacity of a disk image.
pub trait DiskSize: Send + Debug {
/// Virtual size of the disk image in bytes (reported capacity).
fn logical_size(&self) -> BlockResult<u64>;
}
/// Host allocation size of a file-backed disk image.
pub trait PhysicalSize: Send + Debug {
/// Actual bytes occupied on the host filesystem.
fn physical_size(&self) -> BlockResult<u64>;
}
/// Backing file descriptor access for disk images backed by a file.
pub trait DiskFd: Send + Debug {
/// Borrows the underlying file descriptor.
fn fd(&self) -> BorrowedDiskFd<'_>;
}
/// Sector and cluster geometry of a disk image.
///
/// Default returns `DiskTopology::default()` (512B logical/physical).
pub trait Geometry: Send + Debug {
/// Returns the disk topology.
fn topology(&self) -> DiskTopology {
DiskTopology::default()
}
}
/// Sparse and zero flag support for thin provisioned disk images.
pub trait SparseCapable: Send + Debug {
/// Indicates support for sparse operations (punch hole, write zeroes, discard).
fn supports_sparse_operations(&self) -> bool {
false
}
/// Indicates support for a metadata level zero flag optimization in
/// virtio `VIRTIO_BLK_T_WRITE_ZEROES` requests. When true, the format
/// can mark regions as reading zeros via a metadata bit rather than
/// writing actual zero bytes to disk.
fn supports_zero_flag(&self) -> bool {
false
}
}
/// Live disk resize support.
///
/// Implementations may return an error if the backend does not
/// support resizing (e.g. fixed size formats).
pub trait Resizable: Send + Debug {
/// Resizes the disk image to the given size in bytes, if the backend supports it.
fn resize(&mut self, size: u64) -> BlockResult<()>;
}
/// Supertrait bundling universal disk capabilities.
///
/// Every disk format implements `DiskSize` and `Geometry`.
/// `Sync` is required so that `Arc<dyn DiskFile>` can be shared
/// across threads for concurrent readonly access.
pub trait DiskFile: DiskSize + Geometry + Sync {}
/// Full capability disk file trait.
///
/// Bundles all optional capabilities on top of [`DiskFile`]:
/// file descriptor access, physical size, sparse operations, and resize.
/// Used by consumers that need feature negotiation without async I/O
/// (e.g. vhost user block).
pub trait FullDiskFile: DiskFile + PhysicalSize + DiskFd + SparseCapable + Resizable {}
/// Blanket implementation: any type implementing all constituent traits
/// automatically satisfies [`FullDiskFile`].
impl<T: DiskFile + PhysicalSize + DiskFd + SparseCapable + Resizable> FullDiskFile for T {}
/// Extended disk file trait for virtio queue workers.
///
/// Adds cloning and async I/O construction on top of [`DiskFile`].
/// `Unpin` is required so trait objects can be moved freely.
pub trait AsyncDiskFile: DiskFile + Unpin {
/// Creates an independent handle for a queue worker.
///
/// The clone shares internally reference counted state (e.g.
/// `Arc<Metadata>`) with the original, but owns its own file
/// descriptor and I/O completion resources. Each virtio queue
/// gets one clone so that workers can operate in parallel
/// without contending on I/O state.
///
/// Returns `Box<dyn AsyncDiskFile>` (not `AsyncFullDiskFile`)
/// because clones only serve as data plane handles for queue
/// workers. The original remains the control plane for feature
/// negotiation and configuration.
fn try_clone(&self) -> BlockResult<Box<dyn AsyncDiskFile>>;
/// Constructs a per queue async I/O engine.
///
/// # Arguments
///
/// * `ring_depth` - maximum number of in flight I/O operations.
/// Callers typically pass the virtio queue size. Must be greater
/// than zero. Backends that do not use an async ring (e.g. sync
/// fallback implementations) may ignore this value.
fn create_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>>;
}
/// Full capability async disk file trait.
///
/// Combines [`FullDiskFile`] (all optional capabilities) with
/// [`AsyncDiskFile`] (async I/O construction). This is the top level
/// trait for virtio block devices that need both feature negotiation
/// and async queue workers.
///
/// The type narrowing on [`AsyncDiskFile::try_clone`] is intentional:
/// clones only serve as data plane handles for queue workers, while
/// the original `AsyncFullDiskFile` handle remains the control plane
/// for feature negotiation and configuration.
pub trait AsyncFullDiskFile: FullDiskFile + AsyncDiskFile {}
/// Blanket implementation: any type implementing both [`FullDiskFile`]
/// and [`AsyncDiskFile`] automatically satisfies [`AsyncFullDiskFile`].
impl<T: FullDiskFile + AsyncDiskFile> AsyncFullDiskFile for T {}

245
block/src/error.rs Normal file
View File

@@ -0,0 +1,245 @@
// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
//! Unified error handling for the block crate.
//!
//! # Architecture
//!
//! ```text
//! BlockError -- single public error type
//! |-- BlockErrorKind -- small, stable, matchable classification
//! |-- ErrorContext -- optional diagnostic metadata (path, offset, op)
//! +-- source -- format-specific error (boxed)
//! |-- QcowError
//! |-- VhdError / RawError / ...
//! +-- io::Error / etc.
//! ```
use std::error::Error as StdError;
use std::fmt::{self, Display, Formatter};
use std::io;
use std::path::PathBuf;
/// Small, stable classification of block errors.
///
/// Callers match on this for control flow. Adding new format specific
/// errors does not require new variants here.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum BlockErrorKind {
/// An underlying I/O operation failed.
Io,
/// The disk image format is structurally invalid.
InvalidFormat,
/// The disk image requires a feature that is not implemented.
UnsupportedFeature,
/// The image is marked or detected as corrupt.
CorruptImage,
/// An address, offset, or index is outside the valid range.
OutOfBounds,
/// A file or required internal structure could not be found.
NotFound,
/// An internal counter or limit was exceeded.
Overflow,
}
impl Display for BlockErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Io => write!(f, "I/O error"),
Self::InvalidFormat => write!(f, "Invalid format"),
Self::UnsupportedFeature => write!(f, "Unsupported feature"),
Self::CorruptImage => write!(f, "Corrupt image"),
Self::OutOfBounds => write!(f, "Out of bounds"),
Self::NotFound => write!(f, "Not found"),
Self::Overflow => write!(f, "Overflow"),
}
}
}
/// Classification of the operation that was in progress when an error occurred.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum ErrorOp {
/// Opening a disk image file.
Open,
/// Detecting the image format.
DetectImageType,
/// Duplicating a backing-file descriptor.
DupBackingFd,
/// Resizing a disk image.
Resize,
}
impl Display for ErrorOp {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Open => write!(f, "open"),
Self::DetectImageType => write!(f, "detect_image_type"),
Self::DupBackingFd => write!(f, "dup_backing_fd"),
Self::Resize => write!(f, "resize"),
}
}
}
/// Optional diagnostic context attached to a [`BlockError`].
#[derive(Debug, Default, Clone)]
pub struct ErrorContext {
pub path: Option<PathBuf>,
pub offset: Option<u64>,
pub op: Option<ErrorOp>,
}
impl Display for ErrorContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut first = true;
if let Some(path) = &self.path {
write!(f, "path={}", path.display())?;
first = false;
}
if let Some(offset) = self.offset {
if !first {
write!(f, " ")?;
}
write!(f, "offset={offset:#x}")?;
first = false;
}
if let Some(op) = self.op {
if !first {
write!(f, " ")?;
}
write!(f, "op={op}")?;
}
Ok(())
}
}
/// Unified error type for the block crate.
///
/// Pairs a stable [`BlockErrorKind`] classification with an optional
/// boxed source error (format-specific) and optional [`ErrorContext`].
///
/// Display renders kind + context only; the underlying cause is
/// exposed via [`std::error::Error::source()`] for reporters that
/// walk the chain.
#[derive(Debug)]
pub struct BlockError {
kind: BlockErrorKind,
source: Option<Box<dyn StdError + Send + Sync + 'static>>,
ctx: Option<ErrorContext>,
}
impl BlockError {
/// Create a new `BlockError` from a kind and a source error.
pub fn new<E>(kind: BlockErrorKind, source: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
Self {
kind,
source: Some(Box::new(source)),
ctx: None,
}
}
/// Create a `BlockError` from just a kind, with no underlying cause.
pub fn from_kind(kind: BlockErrorKind) -> Self {
Self {
kind,
source: None,
ctx: None,
}
}
/// Attach or replace the source error (builder-style).
pub fn with_source<E>(mut self, source: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
self.source = Some(Box::new(source));
self
}
/// Attach diagnostic context.
pub fn with_ctx(mut self, ctx: ErrorContext) -> Self {
self.ctx = Some(ctx);
self
}
/// Replace the error classification (builder-style).
pub fn with_kind(mut self, kind: BlockErrorKind) -> Self {
self.kind = kind;
self
}
/// Shorthand: attach an operation name.
pub fn with_op(mut self, op: ErrorOp) -> Self {
self.ctx.get_or_insert_with(ErrorContext::default).op = Some(op);
self
}
/// Shorthand: attach a file path.
pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
self.ctx.get_or_insert_with(ErrorContext::default).path = Some(path.into());
self
}
/// Shorthand: attach a byte offset.
pub fn with_offset(mut self, offset: u64) -> Self {
self.ctx.get_or_insert_with(ErrorContext::default).offset = Some(offset);
self
}
/// The error classification.
pub fn kind(&self) -> BlockErrorKind {
self.kind
}
/// The diagnostic context, if any.
pub fn context(&self) -> Option<&ErrorContext> {
self.ctx.as_ref()
}
/// Access the underlying source error, if any.
pub fn source_ref(&self) -> Option<&(dyn StdError + Send + Sync + 'static)> {
self.source.as_deref()
}
/// Try to downcast the source to a concrete type.
pub fn downcast_ref<T: StdError + 'static>(&self) -> Option<&T> {
self.source.as_ref()?.downcast_ref::<T>()
}
/// Consume the error and return the boxed source, if any.
pub fn into_source(self) -> Option<Box<dyn StdError + Send + Sync + 'static>> {
self.source
}
}
impl Display for BlockError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(ctx) = &self.ctx {
write!(f, " ({ctx})")?;
}
Ok(())
}
}
impl StdError for BlockError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.source
.as_ref()
.map(|e| e.as_ref() as &(dyn StdError + 'static))
}
}
/// Convenience: wrap an `io::Error` as `BlockErrorKind::Io`.
impl From<io::Error> for BlockError {
fn from(e: io::Error) -> Self {
Self::new(BlockErrorKind::Io, e)
}
}
pub type BlockResult<T> = Result<T, BlockError>;

300
block/src/factory.rs Normal file
View File

@@ -0,0 +1,300 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
//! Disk image factory.
//!
//! [`open_disk`] is the single entry point for opening a disk image.
//! It opens the file, detects the image format, probes async I/O
//! support, and constructs the appropriate backend. Callers receive
//! a trait object that is ready for use by virtio queue workers.
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use std::sync::OnceLock;
use std::{fmt, fs};
use log::info;
#[cfg(feature = "io_uring")]
use crate::block_io_uring_is_supported;
use crate::disk_file::AsyncFullDiskFile;
use crate::error::{BlockError, BlockErrorKind, BlockResult};
use crate::formats::qcow::QcowDisk;
use crate::formats::raw::{RawBackend, RawDisk};
use crate::formats::vhd::VhdDisk;
use crate::formats::vhdx::VhdxDisk;
use crate::{
ImageType, block_aio_is_supported, detect_image_type, open_disk_image, preallocate_disk,
};
/// Options for opening a disk image via [`open_disk`].
pub struct DiskOpenOptions<'a> {
pub path: &'a Path,
pub readonly: bool,
pub direct: bool,
pub sparse: bool,
pub backing_files: bool,
pub disable_io_uring: bool,
pub disable_aio: bool,
}
/// Result of [`open_disk`], carrying the detected image type alongside
/// the constructed backend.
pub struct OpenedDisk {
pub image_type: ImageType,
pub disk: Box<dyn AsyncFullDiskFile>,
}
impl fmt::Debug for OpenedDisk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OpenedDisk")
.field("image_type", &self.image_type)
.finish_non_exhaustive()
}
}
/// Returns true when io_uring is supported on the running kernel.
///
/// The result is cached so the probe runs at most once per process.
#[cfg(feature = "io_uring")]
fn io_uring_supported() -> bool {
static SUPPORTED: OnceLock<bool> = OnceLock::new();
*SUPPORTED.get_or_init(block_io_uring_is_supported)
}
/// Returns true when Linux AIO is supported on the running kernel.
///
/// The result is cached so the probe runs at most once per process.
fn aio_supported() -> bool {
static SUPPORTED: OnceLock<bool> = OnceLock::new();
*SUPPORTED.get_or_init(block_aio_is_supported)
}
/// Open a disk image and construct the appropriate async backend.
///
/// - Opens the file with the requested access mode and flags.
/// - Detects the image format from the file header.
/// - Probes io_uring and Linux AIO support on the running kernel.
/// - Constructs the most capable backend available for the detected
/// format, preferring io_uring over AIO over synchronous fallback.
///
/// The returned [`OpenedDisk`] exposes the detected [`ImageType`] so
/// callers can perform post construction validation (e.g. type mismatch
/// checks, configuration warnings).
pub fn open_disk(options: &DiskOpenOptions<'_>) -> BlockResult<OpenedDisk> {
let mut fs_options = fs::OpenOptions::new();
fs_options.read(true);
fs_options.write(!options.readonly);
if options.direct {
fs_options.custom_flags(libc::O_DIRECT);
}
let mut file = open_disk_image(options.path, &fs_options)?;
let image_type = detect_image_type(&mut file)?;
let disk: Box<dyn AsyncFullDiskFile> = match image_type {
ImageType::FixedVhd => open_fixed_vhd(file, options)?,
ImageType::Raw => open_raw(file, options)?,
ImageType::Qcow2 => open_qcow2(file, options)?,
ImageType::Vhdx => open_vhdx(file, options)?,
ImageType::Unknown => {
return Err(
BlockError::from_kind(BlockErrorKind::UnsupportedFeature).with_path(options.path)
);
}
};
Ok(OpenedDisk { image_type, disk })
}
fn open_vhdx(
file: fs::File,
options: &DiskOpenOptions<'_>,
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
info!("Opening VHDX disk file with synchronous backend");
Ok(Box::new(
VhdxDisk::new(file, options.direct).map_err(|e| e.with_path(options.path))?,
))
}
fn open_fixed_vhd(
file: fs::File,
options: &DiskOpenOptions<'_>,
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
#[cfg(feature = "io_uring")]
if !options.disable_io_uring {
if io_uring_supported() {
info!("Opening fixed VHD disk file with io_uring backend");
return Ok(Box::new(
VhdDisk::new(file, true, options.direct).map_err(|e| e.with_path(options.path))?,
));
}
info!("io_uring runtime probe failed for fixed VHD, using synchronous backend");
}
info!("Opening fixed VHD disk file with synchronous backend");
Ok(Box::new(
VhdDisk::new(file, false, options.direct).map_err(|e| e.with_path(options.path))?,
))
}
fn open_raw(
file: fs::File,
options: &DiskOpenOptions<'_>,
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
if !options.readonly && !options.sparse {
preallocate_disk(&file, options.path);
}
#[cfg(feature = "io_uring")]
if !options.disable_io_uring {
if io_uring_supported() {
info!("Opening RAW disk file with io_uring backend");
return Ok(Box::new(RawDisk::new(
file,
RawBackend::IoUring,
options.direct,
)));
}
info!("io_uring runtime probe failed for RAW, trying next backend");
}
if !options.disable_aio {
if aio_supported() {
info!("Opening RAW disk file with AIO backend");
return Ok(Box::new(RawDisk::new(
file,
RawBackend::Aio,
options.direct,
)));
}
info!("AIO runtime probe failed for RAW, using synchronous backend");
}
info!("Opening RAW disk file with synchronous backend");
Ok(Box::new(RawDisk::new(
file,
RawBackend::Sync,
options.direct,
)))
}
fn open_qcow2(
file: fs::File,
options: &DiskOpenOptions<'_>,
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
#[cfg(feature = "io_uring")]
if !options.disable_io_uring {
if io_uring_supported() {
info!("Opening QCOW2 disk file with io_uring backend");
return Ok(Box::new(
QcowDisk::new(
file,
options.direct,
options.backing_files,
options.sparse,
true,
)
.map_err(|e| e.with_path(options.path))?,
));
}
info!("io_uring runtime probe failed for QCOW2, using synchronous backend");
}
info!("Opening QCOW2 disk file with synchronous backend");
Ok(Box::new(
QcowDisk::new(
file,
options.direct,
options.backing_files,
options.sparse,
false,
)
.map_err(|e| e.with_path(options.path))?,
))
}
#[cfg(test)]
mod unit_tests {
use std::path::Path;
use vmm_sys_util::tempfile::TempFile;
use super::*;
use crate::formats::qcow;
fn default_options(path: &Path) -> DiskOpenOptions<'_> {
DiskOpenOptions {
path,
readonly: false,
direct: false,
sparse: false,
backing_files: false,
disable_io_uring: true,
disable_aio: true,
}
}
#[test]
fn nonexistent_path_returns_error() {
let path = Path::new("/tmp/no_such_disk_image.raw");
let options = default_options(path);
match open_disk(&options) {
Err(e) => assert_eq!(e.kind(), BlockErrorKind::Io),
Ok(_) => panic!("expected error for nonexistent path"),
}
}
#[test]
fn detect_raw_image() {
let tmp = TempFile::new().unwrap();
tmp.as_file().set_len(1 << 20).unwrap();
let path = tmp.as_path().to_owned();
let options = default_options(&path);
let opened = open_disk(&options).unwrap();
assert_eq!(opened.image_type, ImageType::Raw);
}
#[test]
fn detect_qcow2_image() {
let tmp = qcow::QcowTempDisk::new(100 * 1024 * 1024, None, false, true, false)
.unwrap()
.into_tempfile();
let path = tmp.as_path().to_owned();
let options = default_options(&path);
let opened = open_disk(&options).unwrap();
assert_eq!(opened.image_type, ImageType::Qcow2);
}
#[test]
fn open_readonly() {
let tmp = TempFile::new().unwrap();
tmp.as_file().set_len(1 << 20).unwrap();
let path = tmp.as_path().to_owned();
let mut options = default_options(&path);
options.readonly = true;
let opened = open_disk(&options).unwrap();
assert_eq!(opened.image_type, ImageType::Raw);
}
#[test]
fn sync_fallback_when_async_disabled() {
let tmp = TempFile::new().unwrap();
let size = 1u64 << 20;
tmp.as_file().set_len(size).unwrap();
let path = tmp.as_path().to_owned();
let options = DiskOpenOptions {
path: &path,
readonly: false,
direct: false,
sparse: false,
backing_files: false,
disable_io_uring: true,
disable_aio: true,
};
let opened = open_disk(&options).unwrap();
assert_eq!(opened.image_type, ImageType::Raw);
assert_eq!(opened.disk.logical_size().unwrap(), size);
}
}

View File

@@ -1,99 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::io::{AsRawFd, RawFd};
use crate::BlockBackend;
use crate::vhd::VhdFooter;
#[derive(Debug)]
pub struct FixedVhd {
file: File,
size: u64,
position: u64,
}
impl FixedVhd {
pub fn new(mut file: File) -> std::io::Result<Self> {
let footer = VhdFooter::new(&mut file)?;
Ok(Self {
file,
size: footer.current_size(),
position: 0,
})
}
}
impl AsRawFd for FixedVhd {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl Read for FixedVhd {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self.file.read(buf) {
Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
}
}
impl Write for FixedVhd {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self.file.write(buf) {
Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
}
fn flush(&mut self) -> std::io::Result<()> {
self.file.sync_all()
}
}
impl Seek for FixedVhd {
fn seek(&mut self, newpos: SeekFrom) -> std::io::Result<u64> {
match self.file.seek(newpos) {
Ok(pos) => {
self.position = pos;
Ok(pos)
}
Err(e) => Err(e),
}
}
}
impl BlockBackend for FixedVhd {
fn logical_size(&self) -> Result<u64, crate::Error> {
Ok(self.size)
}
/// Returns the physical size of the underlying file.
fn physical_size(&self) -> Result<u64, crate::Error> {
self.file
.metadata()
.map(|m| m.len())
.map_err(crate::Error::GetFileMetadata)
}
}
impl Clone for FixedVhd {
fn clone(&self) -> Self {
Self {
file: self.file.try_clone().expect("FixedVhd cloning failed"),
size: self.size,
position: self.position,
}
}
}

View File

@@ -1,125 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::fixed_vhd::FixedVhd;
use crate::raw_async::RawFileAsync;
use crate::{BatchRequest, BlockBackend};
pub struct FixedVhdDiskAsync(FixedVhd);
impl FixedVhdDiskAsync {
pub fn new(file: File) -> std::io::Result<Self> {
Ok(Self(FixedVhd::new(file)?))
}
}
impl DiskFile for FixedVhdDiskAsync {
fn logical_size(&mut self) -> DiskFileResult<u64> {
Ok(self.0.logical_size().unwrap())
}
fn physical_size(&mut self) -> DiskFileResult<u64> {
Ok(self.0.physical_size().unwrap())
}
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
FixedVhdAsync::new(
self.0.as_raw_fd(),
ring_depth,
self.0.logical_size().unwrap(),
)
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.0.as_raw_fd())
}
}
pub struct FixedVhdAsync {
raw_file_async: RawFileAsync,
size: u64,
}
impl FixedVhdAsync {
pub fn new(fd: RawFd, ring_depth: u32, size: u64) -> std::io::Result<Self> {
let raw_file_async = RawFileAsync::new(fd, ring_depth)?;
Ok(FixedVhdAsync {
raw_file_async,
size,
})
}
}
impl AsyncIo for FixedVhdAsync {
fn notifier(&self) -> &EventFd {
self.raw_file_async.notifier()
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
return Err(AsyncIoError::ReadVectored(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid offset {}, can't be larger than file size {}",
offset, self.size
),
)));
}
self.raw_file_async.read_vectored(offset, iovecs, user_data)
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
return Err(AsyncIoError::WriteVectored(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid offset {}, can't be larger than file size {}",
offset, self.size
),
)));
}
self.raw_file_async
.write_vectored(offset, iovecs, user_data)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.raw_file_async.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.raw_file_async.next_completed_request()
}
fn batch_requests_enabled(&self) -> bool {
true
}
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
self.raw_file_async.submit_batch_requests(batch_request)
}
}

View File

@@ -1,116 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
use crate::BlockBackend;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::fixed_vhd::FixedVhd;
use crate::raw_sync::RawFileSync;
pub struct FixedVhdDiskSync(FixedVhd);
impl FixedVhdDiskSync {
pub fn new(file: File) -> std::io::Result<Self> {
Ok(Self(FixedVhd::new(file)?))
}
}
impl DiskFile for FixedVhdDiskSync {
fn logical_size(&mut self) -> DiskFileResult<u64> {
Ok(self.0.logical_size().unwrap())
}
fn physical_size(&mut self) -> DiskFileResult<u64> {
self.0.physical_size().map_err(|e| {
let io_inner = match e {
crate::Error::GetFileMetadata(e) => e,
_ => unreachable!(),
};
DiskFileError::Size(io_inner)
})
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
FixedVhdSync::new(self.0.as_raw_fd(), self.0.logical_size().unwrap())
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.0.as_raw_fd())
}
}
pub struct FixedVhdSync {
raw_file_sync: RawFileSync,
size: u64,
}
impl FixedVhdSync {
pub fn new(fd: RawFd, size: u64) -> std::io::Result<Self> {
Ok(FixedVhdSync {
raw_file_sync: RawFileSync::new(fd),
size,
})
}
}
impl AsyncIo for FixedVhdSync {
fn notifier(&self) -> &EventFd {
self.raw_file_sync.notifier()
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
return Err(AsyncIoError::ReadVectored(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid offset {}, can't be larger than file size {}",
offset, self.size
),
)));
}
self.raw_file_sync.read_vectored(offset, iovecs, user_data)
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
return Err(AsyncIoError::WriteVectored(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid offset {}, can't be larger than file size {}",
offset, self.size
),
)));
}
self.raw_file_sync.write_vectored(offset, iovecs, user_data)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.raw_file_sync.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.raw_file_sync.next_completed_request()
}
}

13
block/src/formats/mod.rs Normal file
View File

@@ -0,0 +1,13 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
//! Disk format implementations.
//!
//! Each format lives in its own submodule with a `DiskFile` wrapper,
//! format specific internals, and sync/async I/O workers.
pub mod qcow;
pub mod raw;
pub mod vhd;
pub mod vhdx;

View File

@@ -0,0 +1,179 @@
// Copyright © 2021 Intel Corporation
//
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//! Thread safe backing file readers for QCOW2 images.
use std::fs::File;
use std::io;
use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
use std::os::unix::fs::FileExt;
use std::sync::Arc;
use super::decoder::Decoder;
use super::metadata::{BackingRead, ClusterReadMapping, QcowMetadata};
use super::parser::{BackingFile, BackingKind, Error as QcowError};
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
use crate::formats::qcow::common::decompress_cluster;
/// Raw backing file using position-independent reads on a duplicated fd.
pub(crate) struct RawBacking {
pub(crate) file: File,
pub(crate) virtual_size: u64,
}
// SAFETY: The only I/O operation is read_at which is position independent
// and safe for concurrent use from multiple threads.
unsafe impl Sync for RawBacking {}
impl BackingRead for RawBacking {
fn read_at(&self, address: u64, buf: &mut [u8]) -> io::Result<()> {
if address >= self.virtual_size {
buf.fill(0);
return Ok(());
}
let available = (self.virtual_size - address) as usize;
if available >= buf.len() {
self.file.read_exact_at(buf, address)
} else {
self.file.read_exact_at(&mut buf[..available], address)?;
buf[available..].fill(0);
Ok(())
}
}
}
/// QCOW2 image used as a backing file for another QCOW2 image.
///
/// Resolves guest offsets through the QCOW2 cluster mapping (L1/L2
/// tables, refcounts) before reading the underlying data. Read only
/// because backing files never receive writes. Nested backing chains
/// are handled recursively via the optional `backing_file` field.
pub(crate) struct Qcow2Backing {
pub(crate) metadata: Arc<QcowMetadata>,
pub(crate) data_file: File,
pub(crate) backing_file: Option<Arc<dyn BackingRead>>,
pub(crate) cluster_size: u64,
pub(crate) decoder: Arc<dyn Decoder>,
}
// SAFETY: All reads go through QcowMetadata which uses RwLock
// and read_exact_at which is position independent and thread safe.
unsafe impl Sync for Qcow2Backing {}
impl BackingRead for Qcow2Backing {
fn read_at(&self, address: u64, buf: &mut [u8]) -> io::Result<()> {
let virtual_size = self.metadata.virtual_size();
if address >= virtual_size {
buf.fill(0);
return Ok(());
}
let available = (virtual_size - address) as usize;
if available < buf.len() {
self.read_clusters(address, &mut buf[..available])?;
buf[available..].fill(0);
return Ok(());
}
self.read_clusters(address, buf)
}
}
impl Qcow2Backing {
fn read_clusters(&self, address: u64, buf: &mut [u8]) -> io::Result<()> {
let total_len = buf.len();
let has_backing = self.backing_file.is_some();
let mappings = self
.metadata
.map_clusters_for_read(address, total_len, has_backing)?;
let mut buf_offset = 0usize;
for mapping in mappings {
match mapping {
ClusterReadMapping::Zero { length } => {
buf[buf_offset..buf_offset + length as usize].fill(0);
buf_offset += length as usize;
}
ClusterReadMapping::Allocated {
offset: host_offset,
length,
} => {
self.data_file.read_exact_at(
&mut buf[buf_offset..buf_offset + length as usize],
host_offset,
)?;
buf_offset += length as usize;
}
ClusterReadMapping::Compressed {
host_offset,
compressed_size,
cluster_offset,
length,
} => {
let mut compressed = vec![0u8; compressed_size];
self.data_file.read_exact_at(&mut compressed, host_offset)?;
let decompressed = decompress_cluster(
&compressed,
self.cluster_size as usize,
&*self.decoder,
)?;
buf[buf_offset..buf_offset + length]
.copy_from_slice(&decompressed[cluster_offset..cluster_offset + length]);
buf_offset += length;
}
ClusterReadMapping::Backing {
offset: backing_offset,
length,
} => {
self.backing_file.as_ref().unwrap().read_at(
backing_offset,
&mut buf[buf_offset..buf_offset + length as usize],
)?;
buf_offset += length as usize;
}
}
}
Ok(())
}
}
impl Drop for Qcow2Backing {
fn drop(&mut self) {
self.metadata.shutdown();
}
}
/// Construct a thread safe backing file reader.
pub(super) fn shared_backing_from(bf: BackingFile) -> BlockResult<Arc<dyn BackingRead>> {
let (kind, virtual_size) = bf.into_kind();
let dup_fd = |fd: BorrowedFd<'_>| -> BlockResult<OwnedFd> {
fd.try_clone_to_owned().map_err(|e| {
BlockError::new(
BlockErrorKind::Io,
QcowError::BackingFileIo(String::new(), e),
)
.with_op(ErrorOp::DupBackingFd)
})
};
match kind {
BackingKind::Raw(raw_file) => {
let file = File::from(dup_fd(raw_file.as_fd())?);
Ok(Arc::new(RawBacking { file, virtual_size }))
}
BackingKind::Qcow { inner, backing } => {
let data_file = File::from(dup_fd(inner.raw_file.as_fd())?);
let metadata = Arc::new(QcowMetadata::new(*inner));
Ok(Arc::new(Qcow2Backing {
cluster_size: metadata.cluster_size(),
decoder: metadata.decoder(),
metadata,
data_file,
backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?,
}))
}
}
}

View File

@@ -0,0 +1,161 @@
// Copyright © 2021 Intel Corporation
//
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//! Shared helpers for QCOW2 sync and async backends.
use std::io;
use super::decoder::Decoder;
/// Decompress a full QCOW2 cluster from compressed data.
///
/// Returns a `cluster_size` byte buffer with the decompressed cluster
/// content. Fails if the decoder does not produce exactly `cluster_size`
/// bytes.
pub(super) fn decompress_cluster(
compressed: &[u8],
cluster_size: usize,
decoder: &dyn Decoder,
) -> io::Result<Vec<u8>> {
let mut decompressed = vec![0u8; cluster_size];
let n = decoder
.decode(compressed, &mut decompressed)
.map_err(|_| io::Error::from_raw_os_error(libc::EIO))?;
if n != cluster_size {
return Err(io::Error::from_raw_os_error(libc::EIO));
}
Ok(decompressed)
}
#[cfg(test)]
pub(crate) mod unit_tests {
use std::fs::File;
use std::io::Write;
use std::os::unix::fs::FileExt;
use flate2::Compression;
use flate2::write::DeflateEncoder;
use super::super::decoder::ZlibDecoder;
use super::decompress_cluster;
const COMPRESSED_FLAG: u64 = 1 << 62;
const CLUSTER_USED_FLAG: u64 = 1 << 63;
const COMPRESSED_SECTOR_SIZE: u64 = 512;
const HEADER_CLUSTER_BITS_OFFSET: u64 = 20;
const HEADER_L1_SIZE_OFFSET: u64 = 36;
const HEADER_L1_TABLE_OFFSET: u64 = 40;
const L1_L2_ADDR_MASK: u64 = 0x00ff_ffff_ffff_fe00;
fn make_compressed_l2_entry(host_offset: u64, compressed_len: usize, cluster_bits: u32) -> u64 {
let compressed_size_shift = 62 - (cluster_bits - 8);
let intra_sector_offset = host_offset & (COMPRESSED_SECTOR_SIZE - 1);
let total_bytes = compressed_len as u64 + intra_sector_offset;
let nsectors = total_bytes.div_ceil(COMPRESSED_SECTOR_SIZE);
let addr_part = host_offset & ((1 << compressed_size_shift) - 1);
let size_part = (nsectors - 1) << compressed_size_shift;
COMPRESSED_FLAG | size_part | addr_part
}
/// Compress every allocated cluster in a QCOW2 image file in place.
pub fn compress_allocated_clusters(file: &mut File) {
let mut buf4 = [0u8; 4];
file.read_exact_at(&mut buf4, HEADER_CLUSTER_BITS_OFFSET)
.unwrap();
let cluster_bits = u32::from_be_bytes(buf4);
let cluster_size = 1u64 << cluster_bits;
file.read_exact_at(&mut buf4, HEADER_L1_SIZE_OFFSET)
.unwrap();
let l1_size = u32::from_be_bytes(buf4);
let mut buf8 = [0u8; 8];
file.read_exact_at(&mut buf8, HEADER_L1_TABLE_OFFSET)
.unwrap();
let l1_table_offset = u64::from_be_bytes(buf8);
let entries_per_l2 = cluster_size / 8;
let mut append_offset = file.metadata().unwrap().len();
append_offset = (append_offset + 511) & !511;
for l1_idx in 0..l1_size as u64 {
let l1_entry_offset = l1_table_offset + l1_idx * 8;
file.read_exact_at(&mut buf8, l1_entry_offset).unwrap();
let l1_entry = u64::from_be_bytes(buf8);
let l2_table_addr = l1_entry & L1_L2_ADDR_MASK;
if l2_table_addr == 0 {
continue;
}
for l2_idx in 0..entries_per_l2 {
let l2_entry_offset = l2_table_addr + l2_idx * 8;
file.read_exact_at(&mut buf8, l2_entry_offset).unwrap();
let l2_entry = u64::from_be_bytes(buf8);
if l2_entry & CLUSTER_USED_FLAG == 0 || l2_entry & COMPRESSED_FLAG != 0 {
continue;
}
let host_cluster_addr = l2_entry & L1_L2_ADDR_MASK;
if host_cluster_addr == 0 {
continue;
}
let mut cluster_data = vec![0u8; cluster_size as usize];
file.read_exact_at(&mut cluster_data, host_cluster_addr)
.unwrap();
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&cluster_data).unwrap();
let compressed = encoder.finish().unwrap();
file.write_all_at(&compressed, append_offset).unwrap();
let padded_len = (compressed.len() + 511) & !511;
if padded_len > compressed.len() {
let padding = vec![0u8; padded_len - compressed.len()];
file.write_all_at(&padding, append_offset + compressed.len() as u64)
.unwrap();
}
let new_entry =
make_compressed_l2_entry(append_offset, compressed.len(), cluster_bits);
file.write_all_at(&new_entry.to_be_bytes(), l2_entry_offset)
.unwrap();
append_offset += padded_len as u64;
}
}
file.flush().unwrap();
}
#[test]
fn test_decompress_cluster() {
let cluster_size = 65536;
let original: Vec<u8> = (0..=255).cycle().take(cluster_size).collect();
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = encoder.finish().unwrap();
let result = decompress_cluster(&compressed, cluster_size, &ZlibDecoder {}).unwrap();
assert_eq!(result, original);
}
#[test]
fn test_decompress_cluster_corrupt_input() {
let corrupt = vec![0xffu8; 64];
let err = decompress_cluster(&corrupt, 65536, &ZlibDecoder {}).unwrap_err();
assert_eq!(err.raw_os_error(), Some(libc::EIO));
}
}

View File

@@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: Apache-2.0
use std::{io, result};
use thiserror::Error;
#[derive(Debug, Error)]
@@ -11,20 +13,20 @@ pub enum Error {
#[error("Zlib unexpected status: {0:?}")]
ZlibUnexpectedStatus(flate2::Status),
#[error("Zstd decompress error")]
ZstdDecompress(#[source] std::io::Error),
ZstdDecompress(#[source] io::Error),
#[error("Zstd: failed to fill buffer")]
ZstdFillBuffer(#[source] std::io::Error),
ZstdFillBuffer(#[source] io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
pub(super) type Result<T> = result::Result<T, Error>;
/// Generic trait for decoding zlib/zstd formats
pub trait Decoder {
pub trait Decoder: Send + Sync {
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize>;
}
#[derive(Default)]
pub struct ZlibDecoder {}
pub(super) struct ZlibDecoder {}
impl Decoder for ZlibDecoder {
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
@@ -43,7 +45,7 @@ impl Decoder for ZlibDecoder {
}
#[derive(Default)]
pub struct ZstdDecoder {}
pub(super) struct ZstdDecoder {}
impl Decoder for ZstdDecoder {
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,700 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//! QCOW2 header parsing, validation, and creation.
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::os::unix::fs::FileExt;
use std::str::FromStr;
use bitflags::bitflags;
use vmm_sys_util::file_traits::FileSync;
use zerocopy::big_endian::{U32 as BeU32, U64 as BeU64};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use super::decoder::{Decoder, ZlibDecoder, ZstdDecoder};
use super::parser::{Error, Result};
use super::util::{div_round_up_u32, div_round_up_u64};
use crate::aligned_file::AlignedFile;
use crate::error::{BlockError, BlockErrorKind, BlockResult};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ImageType {
Raw,
Qcow2,
}
impl Display for ImageType {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
ImageType::Raw => write!(f, "raw"),
ImageType::Qcow2 => write!(f, "qcow2"),
}
}
}
impl FromStr for ImageType {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"raw" => Ok(ImageType::Raw),
"qcow2" => Ok(ImageType::Qcow2),
_ => Err(Error::UnsupportedBackingFileFormat(s.to_string())),
}
}
}
#[derive(Clone, Debug)]
pub enum CompressionType {
Zlib,
Zstd,
}
#[derive(Debug, Clone)]
pub struct BackingFileConfig {
pub path: String,
// If this is None, we will autodetect it.
pub format: Option<ImageType>,
}
// Maximum data size supported.
pub(super) const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
// QCOW magic constant that starts the header.
pub(super) const QCOW_MAGIC: u32 = 0x5146_49fb;
// Default to a cluster size of 2^DEFAULT_CLUSTER_BITS
pub(super) const DEFAULT_CLUSTER_BITS: u32 = 16;
// Limit clusters to reasonable sizes. Choose the same limits as qemu. Making the clusters smaller
// increases the amount of overhead for book keeping.
pub(super) const MIN_CLUSTER_BITS: u32 = 9;
pub(super) const MAX_CLUSTER_BITS: u32 = 21;
// The L1 and RefCount table are kept in RAM, only handle files that require less than 35M entries.
// This easily covers 1 TB files. When support for bigger files is needed the assumptions made to
// keep these tables in RAM needs to be thrown out.
pub(super) const MAX_RAM_POINTER_TABLE_SIZE: u64 = 35_000_000;
// 16-bit refcounts.
pub(super) const DEFAULT_REFCOUNT_ORDER: u32 = 4;
pub(super) const V2_BARE_HEADER_SIZE: u32 = 72;
pub(super) const V3_BARE_HEADER_SIZE: u32 = 104;
pub(super) const AUTOCLEAR_FEATURES_OFFSET: u64 = 88;
pub(super) const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1;
// Compression types as defined in https://www.qemu.org/docs/master/interop/qcow2.html
const COMPRESSION_TYPE_ZLIB: u64 = 0; // zlib/deflate <https://www.ietf.org/rfc/rfc1951.txt>
const COMPRESSION_TYPE_ZSTD: u64 = 1; // zstd <http://github.com/facebook/zstd>
// Header extension types
pub(super) const HEADER_EXT_END: u32 = 0x00000000;
// Backing file format name (raw, qcow2)
pub(super) const HEADER_EXT_BACKING_FORMAT: u32 = 0xe2792aca;
// Feature name table
const HEADER_EXT_FEATURE_NAME_TABLE: u32 = 0x6803f857;
// Feature name table entry type incompatible
const FEAT_TYPE_INCOMPATIBLE: u8 = 0;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IncompatFeatures: u64 {
const DIRTY = 1 << 0;
const CORRUPT = 1 << 1;
const DATA_FILE = 1 << 2;
const COMPRESSION = 1 << 3;
const EXTENDED_L2 = 1 << 4;
}
}
impl IncompatFeatures {
/// Features supported by this implementation.
pub(super) const SUPPORTED: IncompatFeatures = IncompatFeatures::DIRTY
.union(IncompatFeatures::CORRUPT)
.union(IncompatFeatures::COMPRESSION);
/// Get the fallback name for a known feature bit.
fn flag_name(bit: u8) -> Option<&'static str> {
Some(match Self::from_bits_truncate(1u64 << bit) {
Self::DIRTY => "dirty bit",
Self::CORRUPT => "corrupt bit",
Self::DATA_FILE => "external data file",
Self::EXTENDED_L2 => "extended L2 entries",
_ => return None,
})
}
}
/// Error type for unsupported incompatible features.
#[derive(Debug, Clone, thiserror::Error)]
pub struct MissingFeatureError {
/// Unsupported feature bits.
features: IncompatFeatures,
/// Feature name table from the qcow2 image.
feature_names: Vec<(u8, String)>,
}
impl MissingFeatureError {
pub(super) fn new(features: IncompatFeatures, feature_names: Vec<(u8, String)>) -> Self {
Self {
features,
feature_names,
}
}
}
impl Display for MissingFeatureError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let names: Vec<String> = (0u8..64)
.filter(|&bit| self.features.bits() & (1u64 << bit) != 0)
.map(|bit| {
// First try the image's feature name table
self.feature_names
.iter()
.find(|(b, _)| *b == bit)
.map(|(_, name)| name.clone())
// Then try hardcoded fallback names
.or_else(|| IncompatFeatures::flag_name(bit).map(|s| s.to_string()))
// Finally, use generic description
.unwrap_or_else(|| format!("unknown feature bit {bit}"))
})
.collect();
write!(f, "Missing features: {}", names.join(", "))
}
}
// The format supports a "header extension area", that crosvm does not use.
const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8;
// Defined by the specification
const MAX_BACKING_FILE_SIZE: u32 = 1023;
/// Contains the information from the header of a qcow file.
#[derive(Clone, Debug)]
pub struct QcowHeader {
pub magic: u32,
pub version: u32,
pub backing_file_offset: u64,
pub backing_file_size: u32,
pub cluster_bits: u32,
pub size: u64,
pub crypt_method: u32,
pub l1_size: u32,
pub l1_table_offset: u64,
pub refcount_table_offset: u64,
pub refcount_table_clusters: u32,
pub nb_snapshots: u32,
pub snapshots_offset: u64,
// v3 entries
pub incompatible_features: u64,
pub compatible_features: u64,
pub autoclear_features: u64,
pub refcount_order: u32,
pub header_size: u32,
pub compression_type: CompressionType,
// Post-header entries
pub backing_file: Option<BackingFileConfig>,
}
/// On-disk layout of the bare qcow2 header shared by v2 and v3 (72 bytes).
#[repr(C)]
#[derive(FromBytes, IntoBytes, KnownLayout, Immutable)]
struct RawHeaderV2 {
magic: BeU32,
version: BeU32,
backing_file_offset: BeU64,
backing_file_size: BeU32,
cluster_bits: BeU32,
size: BeU64,
crypt_method: BeU32,
l1_size: BeU32,
l1_table_offset: BeU64,
refcount_table_offset: BeU64,
refcount_table_clusters: BeU32,
nb_snapshots: BeU32,
snapshots_offset: BeU64,
}
impl RawHeaderV2 {
fn from_header(header: &QcowHeader) -> Self {
Self {
magic: BeU32::new(header.magic),
version: BeU32::new(header.version),
backing_file_offset: BeU64::new(header.backing_file_offset),
backing_file_size: BeU32::new(header.backing_file_size),
cluster_bits: BeU32::new(header.cluster_bits),
size: BeU64::new(header.size),
crypt_method: BeU32::new(header.crypt_method),
l1_size: BeU32::new(header.l1_size),
l1_table_offset: BeU64::new(header.l1_table_offset),
refcount_table_offset: BeU64::new(header.refcount_table_offset),
refcount_table_clusters: BeU32::new(header.refcount_table_clusters),
nb_snapshots: BeU32::new(header.nb_snapshots),
snapshots_offset: BeU64::new(header.snapshots_offset),
}
}
}
/// On-disk layout of the fields v3 adds after the bare header (32 bytes).
#[repr(C)]
#[derive(FromBytes, IntoBytes, KnownLayout, Immutable)]
struct RawHeaderV3Tail {
incompatible_features: BeU64,
compatible_features: BeU64,
autoclear_features: BeU64,
refcount_order: BeU32,
header_size: BeU32,
}
impl RawHeaderV3Tail {
fn from_header(header: &QcowHeader) -> Self {
Self {
incompatible_features: BeU64::new(header.incompatible_features),
compatible_features: BeU64::new(header.compatible_features),
autoclear_features: BeU64::new(header.autoclear_features),
refcount_order: BeU32::new(header.refcount_order),
header_size: BeU32::new(header.header_size),
}
}
}
#[repr(C)]
#[derive(FromBytes, IntoBytes, KnownLayout, Immutable)]
struct ExtensionHeader {
extension_type: BeU32,
length: BeU32,
}
impl ExtensionHeader {
fn end() -> Self {
Self {
extension_type: BeU32::new(HEADER_EXT_END),
length: BeU32::ZERO,
}
}
}
impl QcowHeader {
/// Read header extensions, optionally collecting feature names for error reporting.
pub(super) fn read_header_extensions(
f: &AlignedFile,
header: &mut QcowHeader,
mut feature_table: Option<&mut Vec<(u8, String)>>,
) -> Result<()> {
// Extensions start directly after the header.
let mut offset = header.header_size as u64;
loop {
let mut field = [0u8; size_of::<ExtensionHeader>()];
f.read_exact_at(&mut field, offset)
.map_err(Error::ReadingHeader)?;
offset += field.len() as u64;
let extension =
ExtensionHeader::read_from_bytes(&field).expect("buffer covers extension header");
let ext_type = extension.extension_type.get();
if ext_type == HEADER_EXT_END {
break;
}
let ext_length = extension.length.get();
match ext_type {
HEADER_EXT_BACKING_FORMAT => {
let mut format_bytes = vec![0u8; ext_length as usize];
f.read_exact_at(&mut format_bytes, offset)
.map_err(Error::ReadingHeader)?;
offset += format_bytes.len() as u64;
let format_str = String::from_utf8(format_bytes)
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
if let Some(backing_file) = &mut header.backing_file {
backing_file.format = Some(format_str.parse()?);
}
}
HEADER_EXT_FEATURE_NAME_TABLE if feature_table.is_some() => {
const FEATURE_NAME_ENTRY_SIZE: usize = 1 + 1 + 46; // type + bit + name
let mut data = vec![0u8; ext_length as usize];
f.read_exact_at(&mut data, offset)
.map_err(Error::ReadingHeader)?;
offset += data.len() as u64;
let table = feature_table.as_mut().unwrap();
for entry in data.as_chunks::<FEATURE_NAME_ENTRY_SIZE>().0 {
if entry[0] == FEAT_TYPE_INCOMPATIBLE {
let bit_number = entry[1];
let name_bytes = &entry[2..];
let name_len = name_bytes.iter().position(|&b| b == 0).unwrap_or(46);
let name = String::from_utf8_lossy(&name_bytes[..name_len]).to_string();
table.push((bit_number, name));
}
}
}
_ => {
// Skip unknown extension
offset += ext_length as u64;
}
}
// Skip to the next 8 byte boundary
let padding = (8 - (ext_length % 8)) % 8;
offset += padding as u64;
}
Ok(())
}
/// Creates a QcowHeader from a reference to a file.
pub fn new(f: &AlignedFile) -> Result<QcowHeader> {
// The bare header fits in V3_BARE_HEADER_SIZE plus the optional
// compression field. Read it once, then decode each region as a typed
// view whose layout matches the on-disk header.
let mut buf = [0u8; V3_BARE_HEADER_SIZE as usize + size_of::<u64>()];
f.read_exact_at(&mut buf, 0).map_err(Error::ReadingHeader)?;
// `buf` is always larger than the views, and the views are unaligned,
// so the casts cannot fail.
let (v2, tail) = RawHeaderV2::ref_from_prefix(&buf).expect("buffer covers the v2 header");
let magic = v2.magic.get();
if magic != QCOW_MAGIC {
return Err(Error::InvalidMagic);
}
let version = v2.version.get();
let mut header = QcowHeader {
magic,
version,
backing_file_offset: v2.backing_file_offset.get(),
backing_file_size: v2.backing_file_size.get(),
cluster_bits: v2.cluster_bits.get(),
size: v2.size.get(),
crypt_method: v2.crypt_method.get(),
l1_size: v2.l1_size.get(),
l1_table_offset: v2.l1_table_offset.get(),
refcount_table_offset: v2.refcount_table_offset.get(),
refcount_table_clusters: v2.refcount_table_clusters.get(),
nb_snapshots: v2.nb_snapshots.get(),
snapshots_offset: v2.snapshots_offset.get(),
incompatible_features: 0,
compatible_features: 0,
autoclear_features: 0,
refcount_order: DEFAULT_REFCOUNT_ORDER,
header_size: V2_BARE_HEADER_SIZE,
compression_type: CompressionType::Zlib,
backing_file: None,
};
if version != 2 {
let (v3, rest) =
RawHeaderV3Tail::ref_from_prefix(tail).expect("buffer covers the v3 header");
header.incompatible_features = v3.incompatible_features.get();
header.compatible_features = v3.compatible_features.get();
header.autoclear_features = v3.autoclear_features.get();
header.refcount_order = v3.refcount_order.get();
header.header_size = v3.header_size.get();
if version == 3 && header.header_size > V3_BARE_HEADER_SIZE {
let (compression, _) =
BeU64::ref_from_prefix(rest).expect("buffer covers the compression field");
let raw_compression_type = compression.get() >> (64 - 8);
header.compression_type = if raw_compression_type == COMPRESSION_TYPE_ZLIB {
Ok(CompressionType::Zlib)
} else if raw_compression_type == COMPRESSION_TYPE_ZSTD {
Ok(CompressionType::Zstd)
} else {
Err(Error::UnsupportedCompressionType)
}?;
}
}
if header.backing_file_size > MAX_BACKING_FILE_SIZE {
return Err(Error::BackingFileTooLong(header.backing_file_size as usize));
}
if header.backing_file_offset == 0 && header.backing_file_size != 0 {
return Err(Error::BackingFileSizeWithoutOffset(
header.backing_file_size,
));
}
if header.backing_file_offset != 0 && header.backing_file_size == 0 {
return Err(Error::BackingFileOffsetWithoutSize(
header.backing_file_offset,
));
}
if header.backing_file_offset != 0 {
let cluster_size = 1u64
.checked_shl(header.cluster_bits)
.ok_or(Error::InvalidClusterSize)?;
if header.backing_file_offset < u64::from(header.header_size) {
return Err(Error::BackingFileOverlapsHeader(
header.backing_file_offset,
header.backing_file_size,
header.header_size,
));
}
if header.backing_file_offset >= cluster_size
|| header.backing_file_offset + u64::from(header.backing_file_size) > cluster_size
{
return Err(Error::BackingFileOutsideFirstCluster(
header.backing_file_offset,
header.backing_file_size,
cluster_size,
));
}
let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize];
f.read_exact_at(&mut backing_file_name_bytes, header.backing_file_offset)
.map_err(Error::ReadingHeader)?;
let path = String::from_utf8(backing_file_name_bytes)
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
header.backing_file = Some(BackingFileConfig { path, format: None });
}
if version == 3 {
// Check for unsupported incompatible features first
let features = IncompatFeatures::from_bits_retain(header.incompatible_features);
let unsupported = features - IncompatFeatures::SUPPORTED;
if !unsupported.is_empty() {
// Read extensions only to get feature names for error reporting
let mut feature_table = Vec::new();
if header.header_size > V3_BARE_HEADER_SIZE {
let _ = Self::read_header_extensions(f, &mut header, Some(&mut feature_table));
}
return Err(Error::UnsupportedFeature(MissingFeatureError::new(
unsupported,
feature_table,
)));
}
// Features OK, now read extensions normally
if header.header_size > V3_BARE_HEADER_SIZE {
Self::read_header_extensions(f, &mut header, None)?;
}
}
Ok(header)
}
pub fn get_decoder(&self) -> Box<dyn Decoder> {
match self.compression_type {
CompressionType::Zlib => Box::new(ZlibDecoder {}),
CompressionType::Zstd => Box::new(ZstdDecoder {}),
}
}
pub fn create_for_size_and_path(
version: u32,
size: u64,
backing_file: Option<&str>,
) -> Result<QcowHeader> {
let header_size = if version == 2 {
V2_BARE_HEADER_SIZE
} else {
V3_BARE_HEADER_SIZE + QCOW_EMPTY_HEADER_EXTENSION_SIZE
};
let cluster_bits: u32 = DEFAULT_CLUSTER_BITS;
let cluster_size: u32 = 0x01 << cluster_bits;
let max_length: usize = (cluster_size - header_size) as usize;
if let Some(path) = backing_file
&& path.len() > max_length
{
return Err(Error::BackingFileTooLong(path.len() - max_length));
}
// L2 blocks are always one cluster long. They contain cluster_size/sizeof(u64) addresses.
let entries_per_cluster: u32 = cluster_size / size_of::<u64>() as u32;
let num_clusters: u32 = div_round_up_u64(size, u64::from(cluster_size)) as u32;
let num_l2_clusters: u32 = div_round_up_u32(num_clusters, entries_per_cluster);
let l1_clusters: u32 = div_round_up_u32(num_l2_clusters, entries_per_cluster);
let header_clusters = div_round_up_u32(size_of::<QcowHeader>() as u32, cluster_size);
Ok(QcowHeader {
magic: QCOW_MAGIC,
version,
backing_file_offset: backing_file.map_or(0, |_| {
header_size
+ if version == 3 {
QCOW_EMPTY_HEADER_EXTENSION_SIZE
} else {
0
}
}) as u64,
backing_file_size: backing_file.map_or(0, |x| x.len()) as u32,
cluster_bits: DEFAULT_CLUSTER_BITS,
size,
crypt_method: 0,
l1_size: num_l2_clusters,
l1_table_offset: u64::from(cluster_size),
// The refcount table is after l1 + header.
refcount_table_offset: u64::from(cluster_size * (l1_clusters + 1)),
refcount_table_clusters: {
// Pre-allocate enough clusters for the entire refcount table as it must be
// continuous in the file. Allocate enough space to refcount all clusters, including
// the refcount clusters.
let max_refcount_clusters = max_refcount_clusters(
DEFAULT_REFCOUNT_ORDER,
cluster_size,
num_clusters + l1_clusters + num_l2_clusters + header_clusters,
) as u32;
// The refcount table needs to store the offset of each refcount cluster.
div_round_up_u32(
max_refcount_clusters * size_of::<u64>() as u32,
cluster_size,
)
},
nb_snapshots: 0,
snapshots_offset: 0,
incompatible_features: 0,
compatible_features: 0,
autoclear_features: 0,
refcount_order: DEFAULT_REFCOUNT_ORDER,
header_size,
compression_type: CompressionType::Zlib,
backing_file: backing_file.map(|path| BackingFileConfig {
path: String::from(path),
format: None,
}),
})
}
/// Write the header to `f`.
pub fn write_to(&self, f: &AlignedFile) -> Result<()> {
// Build the header in memory, then write it in one positional write.
let mut buf = Vec::new();
let v2 = RawHeaderV2::from_header(self);
buf.extend_from_slice(v2.as_bytes());
if self.version == 3 {
let v3 = RawHeaderV3Tail::from_header(self);
buf.extend_from_slice(v3.as_bytes());
if self.header_size > V3_BARE_HEADER_SIZE {
let compression_type = match &self.compression_type {
CompressionType::Zlib => COMPRESSION_TYPE_ZLIB,
CompressionType::Zstd => COMPRESSION_TYPE_ZSTD,
};
let compression_type = BeU64::new(compression_type << (64 - 8));
buf.extend_from_slice(compression_type.as_bytes());
}
let end_extension = ExtensionHeader::end();
buf.extend_from_slice(end_extension.as_bytes());
}
f.write_all_at(&buf, 0).map_err(Error::WritingHeader)?;
if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) {
let offset = if self.backing_file_offset > 0 {
self.backing_file_offset
} else {
buf.len() as u64
};
f.write_all_at(backing_file_path.as_bytes(), offset)
.map_err(Error::WritingHeader)?;
}
// Set the file length by writing a zero to the last byte. This also
// zeros the l1 and refcount table clusters.
let cluster_size = 0x01u64 << self.cluster_bits;
let refcount_blocks_size = u64::from(self.refcount_table_clusters) * cluster_size;
f.write_all_at(
&[0u8],
self.refcount_table_offset + refcount_blocks_size - 2,
)
.map_err(Error::WritingHeader)?;
Ok(())
}
/// Write only the incompatible_features field to the file at its fixed offset.
fn write_incompatible_features(&self, file: &AlignedFile) -> BlockResult<()> {
if self.version != 3 {
return Ok(());
}
file.write_all_at(
&self.incompatible_features.to_be_bytes(),
V2_BARE_HEADER_SIZE as u64,
)
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::WritingHeader(e)))?;
Ok(())
}
/// Set or clear the dirty bit for QCOW2 v3 images.
///
/// When `dirty` is true, sets the bit to indicate the image is in use.
/// When `dirty` is false, clears the bit to indicate a clean shutdown.
pub fn set_dirty_bit(&mut self, file: &mut AlignedFile, dirty: bool) -> BlockResult<()> {
if self.version == 3 {
if dirty {
self.incompatible_features |= IncompatFeatures::DIRTY.bits();
} else {
self.incompatible_features &= !IncompatFeatures::DIRTY.bits();
}
self.write_incompatible_features(file)?;
file.fsync()
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?;
}
Ok(())
}
/// Set the corrupt bit for QCOW2 v3 images.
///
/// This marks the image as corrupted. Once set, the image can only be
/// opened read-only until repaired.
pub fn set_corrupt_bit(&mut self, file: &mut AlignedFile) -> BlockResult<()> {
if self.version == 3 {
self.incompatible_features |= IncompatFeatures::CORRUPT.bits();
self.write_incompatible_features(file)?;
file.fsync()
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?;
}
Ok(())
}
pub fn is_corrupt(&self) -> bool {
IncompatFeatures::from_bits_truncate(self.incompatible_features)
.contains(IncompatFeatures::CORRUPT)
}
/// Clear all autoclear feature bits for QCOW2 v3 images.
///
/// These bits indicate features that can be safely disabled when modified
/// by software that doesn't understand them.
pub fn clear_autoclear_features(&mut self, file: &mut AlignedFile) -> Result<()> {
if self.version == 3 && self.autoclear_features != 0 {
self.autoclear_features = 0;
file.write_all_at(&0u64.to_be_bytes(), AUTOCLEAR_FEATURES_OFFSET)
.map_err(Error::WritingHeader)?;
file.fsync().map_err(Error::SyncingHeader)?;
}
Ok(())
}
}
pub(super) fn max_refcount_clusters(
refcount_order: u32,
cluster_size: u32,
num_clusters: u32,
) -> u64 {
// Use u64 as the product of the u32 inputs can overflow.
let refcount_bits = 0x01u64 << u64::from(refcount_order);
let cluster_bits = u64::from(cluster_size) * 8;
let for_data = div_round_up_u64(u64::from(num_clusters) * refcount_bits, cluster_bits);
let for_refcounts = div_round_up_u64(for_data * refcount_bits, cluster_bits);
for_data + for_refcounts
}
/// Returns an Error if the given offset doesn't align to a cluster boundary.
pub(super) fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
if offset & ((0x01 << cluster_bits) - 1) != 0 {
return Err(Error::InvalidOffset(offset));
}
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,413 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
//! QCOW2 disk image format.
//!
//! Provides [`QcowDisk`], the `DiskFile` wrapper for QCOW2 images
//! with backing file and compression support.
mod backing;
mod common;
mod decoder;
mod engine_sync;
#[cfg(feature = "io_uring")]
mod engine_uring;
mod header;
mod metadata;
mod parser;
mod qcow_raw_file;
mod refcount;
mod util;
mod vec_cache;
use std::fs::File;
use std::os::unix::io::AsRawFd;
#[cfg(any(test, feature = "test-utils"))]
use std::path::Path;
use std::sync::Arc;
use std::{fmt, io};
pub use parser::{
BackingFileConfig, CompressionType, Error, ImageType, IncompatFeatures, MissingFeatureError,
QcowHeader,
};
#[cfg(any(test, feature = "test-utils"))]
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
#[cfg(any(test, feature = "test-utils"))]
use vmm_sys_util::tempfile::TempFile;
use self::backing::shared_backing_from;
use self::engine_sync::QcowSync;
#[cfg(feature = "io_uring")]
use self::engine_uring::QcowAsync;
use self::metadata::{BackingRead, QcowMetadata};
use self::parser::{MAX_NESTING_DEPTH, parse_qcow};
use self::qcow_raw_file::QcowRawFile;
use crate::aligned_file::AlignedFile;
#[cfg(any(test, feature = "test-utils"))]
use crate::async_io::GuestMemoryTarget;
use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError};
use crate::disk_file;
#[cfg(any(test, feature = "test-utils"))]
use crate::disk_file::AsyncDiskFile;
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
/// Unified DiskFile wrapper for QCOW2 disk images.
///
/// Holds the in memory QCOW2 metadata, the data file, and an optional
/// backing file. The metadata is wrapped in an `Arc` because
/// [`QcowSync`] and [`QcowAsync`] I/O workers receive a clone when
/// they are created via [`create_async_io`](DiskFile::create_async_io).
/// The backing file is likewise shared with workers through an `Arc`.
///
/// The `sparse` flag controls whether the image advertises discard
/// support to the guest. The `use_io_uring` flag selects between the
/// [`QcowSync`] and [`QcowAsync`] I/O backends. Both are recorded at
/// construction time and propagated through [`try_clone`](DiskFile::try_clone).
pub struct QcowDisk {
metadata: Arc<QcowMetadata>,
backing_file: Option<Arc<dyn BackingRead>>,
sparse: bool,
data_raw_file: QcowRawFile,
use_io_uring: bool,
}
impl fmt::Debug for QcowDisk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("QcowDisk")
.field("sparse", &self.sparse)
.field("has_backing", &self.backing_file.is_some())
.field("use_io_uring", &self.use_io_uring)
.finish_non_exhaustive()
}
}
impl QcowDisk {
pub fn new(
file: File,
direct_io: bool,
backing_files: bool,
sparse: bool,
use_io_uring: bool,
) -> BlockResult<Self> {
#[cfg(not(feature = "io_uring"))]
if use_io_uring {
return Err(BlockError::new(
BlockErrorKind::UnsupportedFeature,
DiskFileError::NewAsyncIo(io::Error::other(
"io_uring requested but feature is not enabled",
)),
));
}
let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 };
let raw_file = AlignedFile::new(file, direct_io);
let (inner, backing_file, sparse) = parse_qcow(raw_file, max_nesting_depth, sparse)
.map_err(|e| {
let e = if !backing_files && matches!(e.kind(), BlockErrorKind::Overflow) {
e.with_kind(BlockErrorKind::UnsupportedFeature)
} else {
e
};
e.with_op(ErrorOp::Open)
})?;
let data_raw_file = inner.raw_file.clone();
Ok(QcowDisk {
metadata: Arc::new(QcowMetadata::new(inner)),
backing_file: backing_file.map(shared_backing_from).transpose()?,
sparse,
data_raw_file,
use_io_uring,
})
}
/// Synchronous write convenience for tests and benchmarks.
#[cfg(any(test, feature = "test-utils"))]
pub fn write_all_at(&self, offset: u64, data: &[u8]) {
let mut async_io = self.create_async_io(1).unwrap();
let mem =
Arc::new(GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), data.len())]).unwrap());
mem.write_slice(data, GuestAddress(0)).unwrap();
let range = [(GuestAddress(0), data.len() as u32)];
let target = GuestMemoryTarget::new(Arc::clone(&mem), &range).unwrap();
async_io
.write_from_memory(offset as libc::off_t, target, 0)
.unwrap();
while async_io.next_completed_request().is_some() {}
}
/// Synchronous read convenience for tests and benchmarks.
#[cfg(test)]
pub fn read_all_at(&self, offset: u64, len: usize) -> Vec<u8> {
let mut async_io = self.create_async_io(1).unwrap();
let mem = Arc::new(GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), len)]).unwrap());
let range = [(GuestAddress(0), len as u32)];
let target = GuestMemoryTarget::new(Arc::clone(&mem), &range).unwrap();
async_io
.read_to_memory(offset as libc::off_t, target, 0)
.unwrap();
while async_io.next_completed_request().is_some() {}
let mut buf = vec![0u8; len];
mem.read_slice(&mut buf, GuestAddress(0)).unwrap();
buf
}
#[cfg(test)]
fn metadata(&self) -> &QcowMetadata {
&self.metadata
}
}
/// Writes a fresh qcow2 layout into `file`
#[cfg(any(test, feature = "test-utils"))]
pub(crate) fn create_image(
file: &File,
virtual_size: u64,
backing_config: Option<&BackingFileConfig>,
) -> BlockResult<()> {
let path = backing_config.map(|cfg| cfg.path.as_str());
let mut header = QcowHeader::create_for_size_and_path(3, virtual_size, path)
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
if let Some(cfg) = backing_config
&& let Some(backing_file) = &mut header.backing_file
{
backing_file.format = cfg.format;
}
let raw = AlignedFile::new(
file.try_clone()
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?,
false,
);
header
.write_to(&raw)
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
let (inner, _backing, _sparse) = parse_qcow(raw, MAX_NESTING_DEPTH, true)?;
// Flush dirty caches and clear the dirty bit
QcowMetadata::new(inner).shutdown();
Ok(())
}
/// Helper struct to create a new qcow2 image in a temporary file.
#[cfg(any(test, feature = "test-utils"))]
pub struct QcowTempDisk {
tmp: TempFile,
disk: QcowDisk,
}
#[cfg(any(test, feature = "test-utils"))]
impl QcowTempDisk {
/// Creates a new qcow2 image in a temporary file with optional
/// backing file. Flags are passed to QcowDisk::new.
pub fn new(
virtual_size: u64,
backing_config: Option<&BackingFileConfig>,
direct_io: bool,
sparse: bool,
use_io_uring: bool,
) -> BlockResult<Self> {
let tmp = TempFile::new().map_err(io::Error::from)?;
create_image(tmp.as_file(), virtual_size, backing_config)?;
let file = tmp
.as_file()
.try_clone()
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?;
let disk = QcowDisk::new(
file,
direct_io,
backing_config.is_some(),
sparse,
use_io_uring,
)?;
Ok(Self { tmp, disk })
}
pub fn path(&self) -> &Path {
self.tmp.as_path()
}
pub fn as_file(&self) -> &File {
self.tmp.as_file()
}
pub fn disk(&self) -> &QcowDisk {
&self.disk
}
/// Drops the disk handle and returns the underlying TempFile.
pub fn into_tempfile(self) -> TempFile {
self.tmp
}
}
impl Drop for QcowDisk {
fn drop(&mut self) {
self.metadata.shutdown();
}
}
impl disk_file::DiskSize for QcowDisk {
fn logical_size(&self) -> BlockResult<u64> {
Ok(self.metadata.virtual_size())
}
}
impl disk_file::PhysicalSize for QcowDisk {
fn physical_size(&self) -> BlockResult<u64> {
Ok(self.data_raw_file.physical_size()?)
}
}
impl disk_file::DiskFd for QcowDisk {
fn fd(&self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.data_raw_file.as_raw_fd())
}
}
impl disk_file::Geometry for QcowDisk {}
impl disk_file::SparseCapable for QcowDisk {
fn supports_sparse_operations(&self) -> bool {
true
}
fn supports_zero_flag(&self) -> bool {
true
}
}
impl disk_file::Resizable for QcowDisk {
fn resize(&mut self, size: u64) -> BlockResult<()> {
if self.backing_file.is_some() {
return Err(BlockError::new(
BlockErrorKind::UnsupportedFeature,
DiskFileError::ResizeError(io::Error::other(
"resize not supported with backing files",
)),
)
.with_op(ErrorOp::Resize));
}
self.metadata.resize(size).map_err(|e| {
BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e))
.with_op(ErrorOp::Resize)
})
}
}
impl disk_file::DiskFile for QcowDisk {}
impl disk_file::AsyncDiskFile for QcowDisk {
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
Ok(Box::new(QcowDisk {
metadata: Arc::clone(&self.metadata),
backing_file: self.backing_file.as_ref().map(Arc::clone),
sparse: self.sparse,
data_raw_file: self.data_raw_file.clone(),
use_io_uring: self.use_io_uring,
}))
}
fn create_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
if self.use_io_uring {
#[cfg(feature = "io_uring")]
{
return Ok(Box::new(
QcowAsync::new(
Arc::clone(&self.metadata),
self.data_raw_file.clone(),
self.backing_file.as_ref().map(Arc::clone),
self.sparse,
ring_depth,
)
.map_err(|e| {
BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e))
})?,
));
}
#[cfg(not(feature = "io_uring"))]
unreachable!("use_io_uring is set but io_uring feature is not enabled");
}
let _ = ring_depth;
Ok(Box::new(QcowSync::new(
Arc::clone(&self.metadata),
self.data_raw_file.clone(),
self.backing_file.as_ref().map(Arc::clone),
self.sparse,
)))
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
use crate::async_io::AsyncIo;
use crate::disk_file::{AsyncDiskFile, DiskSize, PhysicalSize};
const TEST_SIZE: u64 = 0x5566_7788;
fn make_qcow_file() -> File {
QcowTempDisk::new(TEST_SIZE, None, false, true, false)
.unwrap()
.into_tempfile()
.into_file()
}
#[test]
fn new_sync_returns_correct_size() {
let file = make_qcow_file();
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
assert_eq!(disk.logical_size().unwrap(), TEST_SIZE);
}
fn assert_async_io_from_dyn(disk: &dyn AsyncDiskFile, expect_batch: bool) {
let io: Box<dyn AsyncIo> = disk.create_async_io(128).unwrap();
assert_eq!(io.batch_requests_enabled(), expect_batch);
}
fn assert_async_io(disk: &QcowDisk, expect_batch: bool) {
assert_async_io_from_dyn(disk, expect_batch);
}
#[test]
fn sync_backend_disables_batch_requests() {
let file = make_qcow_file();
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
assert_async_io(&disk, false);
}
#[cfg(feature = "io_uring")]
#[test]
fn io_uring_backend_enables_batch_requests() {
let file = make_qcow_file();
let disk = QcowDisk::new(file, false, false, true, true).unwrap();
assert_async_io(&disk, true);
}
#[test]
fn try_clone_preserves_sync_dispatch() {
let file = make_qcow_file();
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
let cloned = disk.try_clone().unwrap();
assert_async_io_from_dyn(cloned.as_ref(), false);
}
#[cfg(feature = "io_uring")]
#[test]
fn try_clone_preserves_io_uring_dispatch() {
let file = make_qcow_file();
let disk = QcowDisk::new(file, false, false, true, true).unwrap();
let cloned = disk.try_clone().unwrap();
assert_async_io_from_dyn(cloned.as_ref(), true);
}
#[test]
fn physical_size_less_than_logical() {
// make_qcow_file() writes no guest data, so the file on disk
// only contains QCOW2 headers and metadata tables.
let file = make_qcow_file();
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
assert!(disk.physical_size().unwrap() < disk.logical_size().unwrap());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,557 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::fmt::Debug;
use std::io::{self, Write};
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::os::unix::fs::FileExt;
use byteorder::{BigEndian, WriteBytesExt};
use vmm_sys_util::write_zeroes::WriteZeroesAt;
use crate::aligned_file::AlignedFile;
// Type aliases for the refcount read/write function pointers
type RefcountReader = fn(&mut AlignedFile, u64, usize) -> io::Result<Vec<u64>>;
type RefcountWriter = fn(&mut AlignedFile, u64, &[u64]) -> io::Result<()>;
/// Big-endian file access trait.
pub(super) trait BeUint: Sized + Copy {
fn from_be_slice(bytes: &[u8]) -> u64;
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()>;
}
impl BeUint for u8 {
#[inline(always)]
fn from_be_slice(bytes: &[u8]) -> u64 {
bytes[0] as u64
}
#[inline(always)]
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u8(val)
}
}
impl BeUint for u16 {
#[inline(always)]
fn from_be_slice(bytes: &[u8]) -> u64 {
u16::from_be_bytes([bytes[0], bytes[1]]) as u64
}
#[inline(always)]
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u16::<BigEndian>(val)
}
}
impl BeUint for u32 {
#[inline(always)]
fn from_be_slice(bytes: &[u8]) -> u64 {
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64
}
#[inline(always)]
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u32::<BigEndian>(val)
}
}
impl BeUint for u64 {
#[inline(always)]
fn from_be_slice(bytes: &[u8]) -> u64 {
u64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])
}
#[inline(always)]
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
w.write_u64::<BigEndian>(val)
}
}
/// Read byte-aligned refcounts.
fn read_refcount<T: BeUint>(
file: &mut AlignedFile,
offset: u64,
count: usize,
) -> io::Result<Vec<u64>> {
let bytes_per_entry = size_of::<T>();
let mut data = vec![0u8; count * bytes_per_entry];
file.read_exact_at(&mut data, offset)?;
Ok(data
.chunks_exact(bytes_per_entry)
.map(T::from_be_slice)
.collect())
}
/// Write byte-aligned refcounts.
fn write_refcount<T: BeUint + TryFrom<u64>>(
file: &mut AlignedFile,
offset: u64,
table: &[u64],
) -> io::Result<()>
where
<T as TryFrom<u64>>::Error: Debug,
{
let bytes_per_entry = size_of::<T>();
let mut buffer = Vec::with_capacity(table.len() * bytes_per_entry);
for &val in table {
let converted = T::try_from(val).expect("refcount values are validated on increment");
T::write_be(&mut buffer, converted)?;
}
file.write_all_at(&buffer, offset)
}
/// Read sub-byte refcounts. Bit 0 is the least significant bit.
fn read_refcount_subbyte<const BITS: usize>(
file: &mut AlignedFile,
offset: u64,
count: usize,
) -> io::Result<Vec<u64>> {
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
let entries_per_byte = 8 / BITS;
let mask = (1u64 << BITS) - 1;
let bytes_needed = count.div_ceil(entries_per_byte);
let mut bytes = vec![0u8; bytes_needed];
file.read_exact_at(&mut bytes, offset)?;
let mut table = vec![0u64; count];
for (i, val) in table.iter_mut().enumerate() {
let byte_idx = i / entries_per_byte;
let bit_offset = (i % entries_per_byte) * BITS;
*val = (bytes[byte_idx] as u64 >> bit_offset) & mask;
}
Ok(table)
}
/// Write sub-byte refcounts. Bit 0 is the least significant bit.
fn write_refcount_subbyte<const BITS: usize>(
file: &mut AlignedFile,
offset: u64,
table: &[u64],
) -> io::Result<()> {
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
let entries_per_byte = 8 / BITS;
let mask = (1u64 << BITS) - 1;
let mut buffer = Vec::with_capacity(table.len().div_ceil(entries_per_byte));
for chunk in table.chunks(entries_per_byte) {
let mut byte = 0u8;
for (i, &val) in chunk.iter().enumerate() {
let bit_offset = i * BITS;
byte |= ((val & mask) << bit_offset) as u8;
}
buffer.push(byte);
}
file.write_all_at(&buffer, offset)
}
/// A qcow file. Allows reading/writing clusters and appending clusters.
#[derive(Debug)]
pub(super) struct QcowRawFile {
file: AlignedFile,
cluster_size: u64,
cluster_mask: u64,
refcount_block_entries: u64,
read_refcount_fn: RefcountReader,
write_refcount_fn: RefcountWriter,
}
impl QcowRawFile {
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
/// a power of two or refcount_bits is invalid.
pub(super) fn from(file: AlignedFile, cluster_size: u64, refcount_bits: u64) -> Option<Self> {
if !cluster_size.is_power_of_two() {
return None;
}
let (read_refcount_fn, write_refcount_fn): (RefcountReader, RefcountWriter) =
match refcount_bits {
1 => (read_refcount_subbyte::<1>, write_refcount_subbyte::<1>),
2 => (read_refcount_subbyte::<2>, write_refcount_subbyte::<2>),
4 => (read_refcount_subbyte::<4>, write_refcount_subbyte::<4>),
8 => (read_refcount::<u8>, write_refcount::<u8>),
16 => (read_refcount::<u16>, write_refcount::<u16>),
32 => (read_refcount::<u32>, write_refcount::<u32>),
64 => (read_refcount::<u64>, write_refcount::<u64>),
_ => return None,
};
// For sub-byte refcounts (1,2,4 bits), entries pack multiple per byte
let refcount_block_entries = cluster_size * 8 / refcount_bits;
Some(QcowRawFile {
file,
cluster_size,
cluster_mask: cluster_size - 1,
refcount_block_entries,
read_refcount_fn,
write_refcount_fn,
})
}
/// Reads `count` 64 bit offsets and returns them as a vector.
/// `mask` optionally `&`s out some of the bits on the file.
pub(super) fn read_pointer_table(
&mut self,
offset: u64,
count: u64,
mask: Option<u64>,
) -> io::Result<Vec<u64>> {
let mut bytes = vec![0u8; count as usize * size_of::<u64>()];
self.file.read_exact_at(&mut bytes, offset)?;
let m = mask.unwrap_or(u64::MAX);
let table = bytes
.as_chunks::<{ size_of::<u64>() }>()
.0
.iter()
.map(|c| u64::from_be_bytes(*c) & m)
.collect();
Ok(table)
}
/// Reads a cluster's worth of 64 bit offsets and returns them as a vector.
/// `mask` optionally `&`s out some of the bits on the file.
pub(super) fn read_pointer_cluster(
&mut self,
offset: u64,
mask: Option<u64>,
) -> io::Result<Vec<u64>> {
let count = self.cluster_size / size_of::<u64>() as u64;
self.read_pointer_table(offset, count, mask)
}
/// Writes a pointer table to `offset` in the file.
/// Entries are computed on-the-fly by the callback.
///
/// The callback may perform metadata I/O on this `QcowRawFile`, so all
/// entries are materialized before the final positional write.
pub(super) fn write_pointer_table<'a, T: Copy + 'a>(
&mut self,
offset: u64,
entries: impl Iterator<Item = &'a T>,
mut f: impl FnMut(&mut QcowRawFile, T) -> io::Result<u64>,
) -> io::Result<()> {
let mut buffer = Vec::with_capacity(entries.size_hint().0 * size_of::<u64>());
for addr in entries {
let entry = f(self, *addr)?;
buffer.extend_from_slice(&entry.to_be_bytes());
}
self.file.write_all_at(&buffer, offset)
}
/// Writes a pointer table directly without transforming values.
///
/// Uses the same materialize-then-write path as `write_pointer_table`.
pub(super) fn write_pointer_table_direct<'a>(
&mut self,
offset: u64,
entries: impl Iterator<Item = &'a u64>,
) -> io::Result<()> {
let mut buffer = Vec::with_capacity(entries.size_hint().0 * size_of::<u64>());
for &entry in entries {
buffer.extend_from_slice(&entry.to_be_bytes());
}
self.file.write_all_at(&buffer, offset)
}
/// Read a refcount block from the file and returns a Vec containing the block.
/// Always returns a cluster's worth of data.
#[inline]
pub(super) fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u64>> {
(self.read_refcount_fn)(&mut self.file, offset, self.refcount_block_entries as usize)
}
/// Writes a refcount block to the file.
#[inline]
pub(super) fn write_refcount_block(&mut self, offset: u64, table: &[u64]) -> io::Result<()> {
(self.write_refcount_fn)(&mut self.file, offset, table)
}
/// Allocates a new cluster at the end of the current file, return the address.
pub(super) fn add_cluster_end(
&mut self,
max_valid_cluster_offset: u64,
) -> io::Result<Option<u64>> {
// Determine where the new end of the file should be and set_len, which
// translates to truncate(2).
let file_end: u64 = self.physical_size()?;
let new_cluster_address: u64 = (file_end + self.cluster_size - 1) & !self.cluster_mask;
if new_cluster_address > max_valid_cluster_offset {
return Ok(None);
}
self.file.set_len(new_cluster_address + self.cluster_size)?;
Ok(Some(new_cluster_address))
}
/// Returns a reference to the underlying file.
pub(super) fn file(&self) -> &AlignedFile {
&self.file
}
/// Returns a mutable reference to the underlying file.
pub(super) fn file_mut(&mut self) -> &mut AlignedFile {
&mut self.file
}
/// Returns the size of the file's clusters.
pub(super) fn cluster_size(&self) -> u64 {
self.cluster_size
}
/// Returns the offset of `address` within a cluster.
pub(super) fn cluster_offset(&self, address: u64) -> u64 {
address & self.cluster_mask
}
/// Returns the base address of the cluster containing `address`.
pub(super) fn cluster_address(&self, address: u64) -> u64 {
address & !self.cluster_mask
}
/// Zeros out a cluster in the file.
pub(super) fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
let cluster_size = self.cluster_size as usize;
self.file.write_all_zeroes_at(address, cluster_size)?;
Ok(())
}
/// Writes
pub(super) fn write_cluster(&mut self, address: u64, data: &[u8]) -> io::Result<()> {
let cluster_size = self.cluster_size as usize;
self.file.write_all_at(&data[0..cluster_size], address)
}
pub(super) fn physical_size(&self) -> io::Result<u64> {
self.file.metadata().map(|m| m.len())
}
}
impl Clone for QcowRawFile {
fn clone(&self) -> Self {
QcowRawFile {
file: self.file.try_clone().expect("QcowRawFile cloning failed"),
cluster_size: self.cluster_size,
cluster_mask: self.cluster_mask,
refcount_block_entries: self.refcount_block_entries,
read_refcount_fn: self.read_refcount_fn,
write_refcount_fn: self.write_refcount_fn,
}
}
}
impl AsRawFd for QcowRawFile {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl AsFd for QcowRawFile {
fn as_fd(&self) -> BorrowedFd<'_> {
self.file.as_fd()
}
}
#[cfg(test)]
mod unit_tests {
use std::io::Read;
use std::os::unix::fs::FileExt;
use vmm_sys_util::tempfile::TempFile;
use super::*;
fn be_bytes(entries: &[u64]) -> Vec<u8> {
let mut v = Vec::with_capacity(size_of_val(entries));
for e in entries {
v.extend_from_slice(&e.to_be_bytes());
}
v
}
fn find_all(haystack: &[u8], needle: &[u8]) -> Vec<usize> {
haystack
.windows(needle.len())
.enumerate()
.filter(|(_, w)| *w == needle)
.map(|(i, _)| i)
.collect()
}
const CLUSTER_SIZE: u64 = 0x10000; // 64 KiB
const TARGET_OFFSET: u64 = 0x1000; // where the table must be written
const FAR_OFFSET: u64 = 0x9000; // where the callback reads (refcount block)
const FILE_LEN: u64 = 0x40000; // 256 KiB filler so all offsets are valid
fn make_qcow_raw() -> (TempFile, QcowRawFile) {
make_qcow_raw_bits(16)
}
fn make_qcow_raw_bits(refcount_bits: u64) -> (TempFile, QcowRawFile) {
let temp_file = TempFile::new().unwrap();
temp_file.as_file().set_len(FILE_LEN).unwrap();
let file = temp_file.as_file().try_clone().unwrap();
let raw = AlignedFile::new(file, false);
let qcow_raw =
QcowRawFile::from(raw, CLUSTER_SIZE, refcount_bits).expect("QcowRawFile::from");
(temp_file, qcow_raw)
}
#[test]
fn write_pointer_table_lands_at_offset_despite_callback_seek() {
let (temp_file, mut qcow) = make_qcow_raw();
let entries: Vec<u64> = vec![0x1111_2222_3333_4444u64; 8]; // 64 bytes
qcow.write_pointer_table(TARGET_OFFSET, entries.iter(), |q, addr| {
let _ = q.read_refcount_block(FAR_OFFSET)?;
Ok(addr)
})
.expect("write_pointer_table");
let expected = be_bytes(&entries);
let mut verify = temp_file.as_file().try_clone().unwrap();
let mut whole = Vec::new();
verify.read_to_end(&mut whole).unwrap();
let found_at = find_all(&whole, &expected);
let mut at_target = vec![0u8; expected.len()];
verify.read_exact_at(&mut at_target, TARGET_OFFSET).unwrap();
assert_eq!(
at_target, expected,
"pointer table did NOT land at TARGET_OFFSET {TARGET_OFFSET:#x}; \
found matching bytes at {found_at:x?}"
);
}
#[test]
fn write_pointer_table_direct_lands_at_offset() {
let (temp_file, mut qcow) = make_qcow_raw();
let entries: Vec<u64> = vec![0xAAAA_BBBB_CCCC_DDDDu64; 8];
qcow.write_pointer_table_direct(TARGET_OFFSET, entries.iter())
.expect("write_pointer_table_direct");
let expected = be_bytes(&entries);
let verify = temp_file.as_file().try_clone().unwrap();
let mut at_target = vec![0u8; expected.len()];
verify.read_exact_at(&mut at_target, TARGET_OFFSET).unwrap();
assert_eq!(
at_target, expected,
"write_pointer_table_direct did not land at {TARGET_OFFSET:#x}"
);
}
#[test]
fn read_pointer_table_round_trips() {
let (_temp_file, mut qcow) = make_qcow_raw();
let entries: Vec<u64> = vec![
0x0000_0000_0000_0000,
0x0011_2233_4455_6677,
0x8899_aabb_ccdd_eeff,
0xffff_ffff_ffff_ffff,
];
qcow.write_pointer_table_direct(TARGET_OFFSET, entries.iter())
.expect("write_pointer_table_direct");
let read_back = qcow
.read_pointer_table(TARGET_OFFSET, entries.len() as u64, None)
.expect("read_pointer_table");
assert_eq!(read_back, entries);
}
#[test]
fn read_pointer_table_applies_mask() {
let (_temp_file, mut qcow) = make_qcow_raw();
let entries: Vec<u64> = vec![0xffff_ffff_ffff_ffffu64; 4];
let mask = 0x00ff_ffff_ffff_fe00u64;
qcow.write_pointer_table_direct(TARGET_OFFSET, entries.iter())
.expect("write_pointer_table_direct");
let read_back = qcow
.read_pointer_table(TARGET_OFFSET, entries.len() as u64, Some(mask))
.expect("read_pointer_table");
assert!(read_back.iter().all(|&e| e == mask));
}
#[test]
fn write_cluster_then_zero_cluster_round_trips() {
let (temp_file, mut qcow) = make_qcow_raw();
let cluster_size = CLUSTER_SIZE as usize;
let data: Vec<u8> = (0..cluster_size).map(|i| (i % 251) as u8).collect();
qcow.write_cluster(CLUSTER_SIZE, &data)
.expect("write_cluster");
let verify = temp_file.as_file().try_clone().unwrap();
let mut buf = vec![0u8; cluster_size];
verify.read_exact_at(&mut buf, CLUSTER_SIZE).unwrap();
assert_eq!(buf, data);
qcow.zero_cluster(CLUSTER_SIZE).expect("zero_cluster");
verify.read_exact_at(&mut buf, CLUSTER_SIZE).unwrap();
assert!(buf.iter().all(|&b| b == 0));
}
#[test]
fn refcount_block_round_trips() {
let (_temp_file, mut qcow) = make_qcow_raw_bits(16);
let count = qcow.refcount_block_entries as usize;
let table: Vec<u64> = (0..count).map(|i| (i % 251) as u64).collect();
qcow.write_refcount_block(TARGET_OFFSET, &table)
.expect("write_refcount_block");
let read_back = qcow
.read_refcount_block(TARGET_OFFSET)
.expect("read_refcount_block");
assert_eq!(read_back, table);
}
#[test]
fn refcount_block_subbyte_round_trips() {
let (_temp_file, mut qcow) = make_qcow_raw_bits(4);
let count = qcow.refcount_block_entries as usize;
let table: Vec<u64> = (0..count).map(|i| (i % 16) as u64).collect();
qcow.write_refcount_block(TARGET_OFFSET, &table)
.expect("write_refcount_block");
let read_back = qcow
.read_refcount_block(TARGET_OFFSET)
.expect("read_refcount_block");
assert_eq!(read_back, table);
}
#[test]
fn add_cluster_end_appends_aligned_cluster() {
let (_temp_file, mut qcow) = make_qcow_raw();
let before = qcow.physical_size().unwrap();
let addr = qcow
.add_cluster_end(u64::MAX)
.expect("add_cluster_end")
.expect("a cluster was allocated");
assert_eq!(addr % CLUSTER_SIZE, 0);
assert!(addr >= before);
assert_eq!(qcow.physical_size().unwrap(), addr + CLUSTER_SIZE);
}
#[test]
fn add_cluster_end_respects_max_offset() {
let (_temp_file, mut qcow) = make_qcow_raw();
assert!(qcow.add_cluster_end(0).unwrap().is_none());
}
}

View File

@@ -4,13 +4,13 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::io;
use std::{io, result};
use libc::EINVAL;
use thiserror::Error;
use crate::qcow::qcow_raw_file::QcowRawFile;
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
use super::qcow_raw_file::QcowRawFile;
use super::vec_cache::{CacheMap, Cacheable, VecCache};
#[derive(Debug, Error)]
pub enum Error {
@@ -20,6 +20,9 @@ pub enum Error {
/// `InvalidIndex` - Address requested isn't within the range of the disk.
#[error("Address requested is not within the range of the disk")]
InvalidIndex,
/// `RefblockUnaligned` - Refcount block offset is not cluster aligned.
#[error("Refcount block offset {0:#x} is not cluster aligned")]
RefblockUnaligned(u64),
/// `NeedCluster` - Handle this error by reading the cluster and calling the function again.
#[error("Cluster with addr={0} needs to be read")]
NeedCluster(u64),
@@ -29,32 +32,43 @@ pub enum Error {
/// `ReadingRefCounts` - Error reading the file into the refcount cache.
#[error("Failed to read the file into the refcount cache")]
ReadingRefCounts(#[source] io::Error),
/// `RefcountOverflow` - Refcount value exceeds maximum for the refcount width.
#[error("Refcount value {value} exceeds {refcount_bits}-bit max ({max})")]
RefcountOverflow {
value: u64,
max: u64,
refcount_bits: u64,
},
}
pub type Result<T> = std::result::Result<T, Error>;
pub(super) type Result<T> = result::Result<T, Error>;
/// Represents the refcount entries for an open qcow file.
#[derive(Clone, Debug)]
pub struct RefCount {
pub(super) struct RefCount {
ref_table: VecCache<u64>,
refcount_table_offset: u64,
refblock_cache: CacheMap<VecCache<u16>>,
refblock_cache: CacheMap<VecCache<u64>>,
refcount_block_entries: u64, // number of refcounts in a cluster.
cluster_size: u64,
max_valid_cluster_offset: u64,
max_refcount: u64, // maximum refcount value for this image's refcount_order
refcount_bits: u64, // number of bits per refcount entry
}
impl RefCount {
/// Creates a `RefCount` from `file`, reading the refcount table from `refcount_table_offset`.
/// `refcount_table_entries` specifies the number of refcount blocks used by this image.
/// `refcount_block_entries` indicates the number of refcounts in each refcount block.
/// `refcount_bits` is the number of bits per refcount (1, 2, 4, 8, 16, 32, or 64).
/// Each refcount table entry points to a refcount block.
pub fn new(
pub(super) fn new(
raw_file: &mut QcowRawFile,
refcount_table_offset: u64,
refcount_table_entries: u64,
refcount_block_entries: u64,
cluster_size: u64,
refcount_bits: u64,
) -> io::Result<RefCount> {
let ref_table = VecCache::from_vec(raw_file.read_pointer_table(
refcount_table_offset,
@@ -63,6 +77,11 @@ impl RefCount {
)?);
let max_valid_cluster_index = (ref_table.len() as u64) * refcount_block_entries - 1;
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
let max_refcount = if refcount_bits >= 64 {
u64::MAX
} else {
(1u64 << refcount_bits) - 1
};
Ok(RefCount {
ref_table,
refcount_table_offset,
@@ -70,16 +89,18 @@ impl RefCount {
refcount_block_entries,
cluster_size,
max_valid_cluster_offset,
max_refcount,
refcount_bits,
})
}
/// Returns the number of refcounts per block.
pub fn refcounts_per_block(&self) -> u64 {
pub(super) fn refcounts_per_block(&self) -> u64 {
self.refcount_block_entries
}
/// Returns the maximum valid cluster offset in the raw file for this refcount table.
pub fn max_valid_cluster_offset(&self) -> u64 {
pub(super) fn max_valid_cluster_offset(&self) -> u64 {
self.max_valid_cluster_offset
}
@@ -88,13 +109,21 @@ impl RefCount {
/// allocate a cluster or read the required one and call this function again with the cluster.
/// On success, an optional address of a dropped cluster is returned. The dropped cluster can
/// be reused for other purposes.
pub fn set_cluster_refcount(
pub(super) fn set_cluster_refcount(
&mut self,
raw_file: &mut QcowRawFile,
cluster_address: u64,
refcount: u16,
mut new_cluster: Option<(u64, VecCache<u16>)>,
refcount: u64,
mut new_cluster: Option<(u64, VecCache<u64>)>,
) -> Result<Option<u64>> {
if refcount > self.max_refcount {
return Err(Error::RefcountOverflow {
value: refcount,
max: self.max_refcount,
refcount_bits: self.refcount_bits,
});
}
let (table_index, block_index) = self.get_refcount_index(cluster_address);
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
@@ -138,14 +167,14 @@ impl RefCount {
/// Flush the dirty refcount blocks. This must be done before flushing the table that points to
/// the blocks.
pub fn flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()> {
pub(super) fn flush_blocks(&mut self, raw_file: &mut QcowRawFile) -> io::Result<()> {
// Write out all dirty L2 tables.
for (table_index, block) in self.refblock_cache.iter_mut().filter(|(_k, v)| v.dirty()) {
let addr = self.ref_table[*table_index];
if addr != 0 {
raw_file.write_refcount_block(addr, block.get_values())?;
} else {
return Err(std::io::Error::from_raw_os_error(EINVAL));
return Err(io::Error::from_raw_os_error(EINVAL));
}
block.mark_clean();
}
@@ -154,7 +183,7 @@ impl RefCount {
/// Flush the refcount table that keeps the address of the refcounts blocks.
/// Returns true if the table changed since the previous `flush_table()` call.
pub fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> {
pub(super) fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> {
if self.ref_table.dirty() {
raw_file
.write_pointer_table_direct(self.refcount_table_offset, self.ref_table.iter())?;
@@ -166,16 +195,19 @@ impl RefCount {
}
/// Gets the refcount for a cluster with the given address.
pub fn get_cluster_refcount(
pub(super) fn get_cluster_refcount(
&mut self,
raw_file: &mut QcowRawFile,
address: u64,
) -> Result<u16> {
) -> Result<u64> {
let (table_index, block_index) = self.get_refcount_index(address);
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
if block_addr_disk == 0 {
return Ok(0);
}
if block_addr_disk & (self.cluster_size - 1) != 0 {
return Err(Error::RefblockUnaligned(block_addr_disk));
}
if !self.refblock_cache.contains_key(table_index) {
let table = VecCache::from_vec(
raw_file
@@ -192,41 +224,6 @@ impl RefCount {
Ok(self.refblock_cache.get(table_index).unwrap()[block_index])
}
/// Returns the refcount table for this file. This is only useful for debugging.
pub fn ref_table(&self) -> &[u64] {
self.ref_table.get_values()
}
/// Returns the refcounts stored in the given block.
pub fn refcount_block(
&mut self,
raw_file: &mut QcowRawFile,
table_index: usize,
) -> Result<Option<&[u16]>> {
let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
if block_addr_disk == 0 {
return Ok(None);
}
if !self.refblock_cache.contains_key(table_index) {
let table = VecCache::from_vec(
raw_file
.read_refcount_block(block_addr_disk)
.map_err(Error::ReadingRefCounts)?,
);
// TODO(dgreid) - closure needs to return an error.
let ref_table = &self.ref_table;
self.refblock_cache
.insert(table_index, table, |index, evicted| {
raw_file.write_refcount_block(ref_table[index], evicted.get_values())
})
.map_err(Error::EvictingRefCounts)?;
}
// The index must exist as it was just inserted if it didn't already.
Ok(Some(
self.refblock_cache.get(table_index).unwrap().get_values(),
))
}
// Gets the address of the refcount block and the index into the block for the given address.
fn get_refcount_index(&self, address: u64) -> (usize, usize) {
let block_index = (address / self.cluster_size) % self.refcount_block_entries;

View File

@@ -0,0 +1,84 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//! Pure helper functions and constants for QCOW2 L1/L2 table entry
//! manipulation and integer arithmetic. Shared across the `qcow` submodules.
/// Nesting depth limit for disk formats that can open other disk files.
pub(crate) const MAX_NESTING_DEPTH: u32 = 10;
// bits 0-8 and 56-63 are reserved.
pub(super) const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
pub(super) const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
// Flags
pub(super) const ZERO_FLAG: u64 = 1 << 0;
pub(super) const COMPRESSED_FLAG: u64 = 1 << 62;
pub(super) const COMPRESSED_SECTOR_SIZE: u64 = 512;
pub(super) const CLUSTER_USED_FLAG: u64 = 1 << 63;
/// Check if L2 entry is empty (unallocated).
pub(super) fn l2_entry_is_empty(l2_entry: u64) -> bool {
l2_entry == 0
}
/// Check bit 0 - only valid for standard clusters.
pub(super) fn l2_entry_is_zero(l2_entry: u64) -> bool {
l2_entry & ZERO_FLAG != 0
}
/// Check if L2 entry refers to a compressed cluster.
pub(super) fn l2_entry_is_compressed(l2_entry: u64) -> bool {
l2_entry & COMPRESSED_FLAG != 0
}
/// Get file offset and size of compressed cluster data.
pub(super) fn l2_entry_compressed_cluster_layout(l2_entry: u64, cluster_bits: u32) -> (u64, usize) {
let compressed_size_shift = 62 - (cluster_bits - 8);
let compressed_size_mask = (1 << (cluster_bits - 8)) - 1;
let compressed_cluster_addr = l2_entry & ((1 << compressed_size_shift) - 1);
let nsectors = (l2_entry >> compressed_size_shift & compressed_size_mask) + 1;
let compressed_cluster_size = ((nsectors * COMPRESSED_SECTOR_SIZE)
- (compressed_cluster_addr & (COMPRESSED_SECTOR_SIZE - 1)))
as usize;
(compressed_cluster_addr, compressed_cluster_size)
}
/// Get file offset of standard (non-compressed) cluster.
pub(super) fn l2_entry_std_cluster_addr(l2_entry: u64) -> u64 {
l2_entry & L2_TABLE_OFFSET_MASK
}
/// Make L2 entry for standard (non-compressed) cluster.
pub(super) fn l2_entry_make_std(cluster_addr: u64) -> u64 {
(cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG
}
/// Make L2 entry for preallocated zero cluster.
pub(super) fn l2_entry_make_zero(cluster_addr: u64) -> u64 {
(cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG | ZERO_FLAG
}
/// Make L2 entry for an unallocated cluster that reads as logical zeros.
pub(super) fn l2_entry_make_zero_plain() -> u64 {
ZERO_FLAG
}
/// Make L1 entry with optional flags.
pub(super) fn l1_entry_make(cluster_addr: u64, refcount_is_one: bool) -> u64 {
(cluster_addr & L1_TABLE_OFFSET_MASK) | (refcount_is_one as u64 * CLUSTER_USED_FLAG)
}
/// Ceiling of the division of `dividend`/`divisor`.
pub(super) fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
dividend / divisor + u32::from(!dividend.is_multiple_of(divisor))
}
/// Ceiling of the division of `dividend`/`divisor`.
pub(super) fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
dividend / divisor + u64::from(!dividend.is_multiple_of(divisor))
}

View File

@@ -12,21 +12,21 @@ use std::slice::SliceIndex;
/// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so
/// it can be checked if they need to be committed to disk.
pub trait Cacheable {
pub(super) trait Cacheable {
/// Used to check if the item needs to be written out or if it can be discarded.
fn dirty(&self) -> bool;
}
#[derive(Clone, Debug)]
/// Represents a vector that implements the `Cacheable` trait so it can be held in a cache.
pub struct VecCache<T: 'static + Copy + Default> {
pub(super) struct VecCache<T: 'static + Copy + Default> {
vec: Box<[T]>,
dirty: bool,
}
impl<T: 'static + Copy + Default> VecCache<T> {
/// Creates a `VecCache` that can hold `count` elements.
pub fn new(count: usize) -> VecCache<T> {
pub(super) fn new(count: usize) -> VecCache<T> {
VecCache {
vec: vec![Default::default(); count].into_boxed_slice(),
dirty: true,
@@ -34,14 +34,14 @@ impl<T: 'static + Copy + Default> VecCache<T> {
}
/// Creates a `VecCache` from the passed in `vec`.
pub fn from_vec(vec: Vec<T>) -> VecCache<T> {
pub(super) fn from_vec(vec: Vec<T>) -> VecCache<T> {
VecCache {
vec: vec.into_boxed_slice(),
dirty: false,
}
}
pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output>
pub(super) fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output>
where
I: SliceIndex<[T]>,
{
@@ -49,19 +49,34 @@ impl<T: 'static + Copy + Default> VecCache<T> {
}
/// Gets a reference to the underlying vector.
pub fn get_values(&self) -> &[T] {
pub(super) fn get_values(&self) -> &[T] {
&self.vec
}
/// Mark this cache element as clean.
pub fn mark_clean(&mut self) {
pub(super) fn mark_clean(&mut self) {
self.dirty = false;
}
/// Returns the number of elements in the vector.
pub fn len(&self) -> usize {
pub(super) fn len(&self) -> usize {
self.vec.len()
}
/// Extends the cache capacity to `new_len` elements.
///
/// No-op if `new_len <= self.len()`. Allocates a new buffer, copies
/// existing data, and fills new elements with default values.
/// Marks the cache as dirty.
pub(super) fn extend(&mut self, new_len: usize) {
if new_len <= self.vec.len() {
return;
}
let mut new_vec = vec![Default::default(); new_len];
new_vec[..self.vec.len()].copy_from_slice(&self.vec);
self.vec = new_vec.into_boxed_slice();
self.dirty = true;
}
}
impl<T: 'static + Copy + Default> Cacheable for VecCache<T> {
@@ -94,37 +109,37 @@ impl<T: 'static + Copy + Default> Deref for VecCache<T> {
}
#[derive(Clone, Debug)]
pub struct CacheMap<T: Cacheable> {
pub(super) struct CacheMap<T: Cacheable> {
capacity: usize,
map: HashMap<usize, T>,
}
impl<T: Cacheable> CacheMap<T> {
pub fn new(capacity: usize) -> Self {
pub(super) fn new(capacity: usize) -> Self {
CacheMap {
capacity,
map: HashMap::with_capacity(capacity),
}
}
pub fn contains_key(&self, key: usize) -> bool {
pub(super) fn contains_key(&self, key: usize) -> bool {
self.map.contains_key(&key)
}
pub fn get(&self, index: usize) -> Option<&T> {
pub(super) fn get(&self, index: usize) -> Option<&T> {
self.map.get(&index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
pub(super) fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.map.get_mut(&index)
}
pub fn iter_mut(&mut self) -> IterMut<'_, usize, T> {
pub(super) fn iter_mut(&mut self) -> IterMut<'_, usize, T> {
self.map.iter_mut()
}
// Check if the refblock cache is full and we need to evict.
pub fn insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()>
pub(super) fn insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()>
where
F: FnOnce(usize, T) -> io::Result<()>,
{

View File

@@ -0,0 +1,159 @@
// Copyright © 2023 Intel Corporation
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//
// Copyright © 2023 Crusoe Energy Systems LLC
//
use std::os::unix::io::AsRawFd;
use vmm_sys_util::eventfd::EventFd;
use super::{operation_is_aligned, run_unaligned_operation};
use crate::async_io::{
AioDataIo, AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult,
};
use crate::error::{BlockError, BlockErrorKind, BlockResult};
use crate::sparse::{punch_hole, write_zeroes};
use crate::{AlignedFile, is_block_device};
pub(super) struct RawAio {
raw_file: AlignedFile,
data_io: AioDataIo,
alignment: u64,
is_block_device: bool,
}
impl RawAio {
pub(super) fn new(raw_file: AlignedFile, queue_depth: u32) -> BlockResult<Self> {
let data_io =
AioDataIo::new(queue_depth).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
let is_block_device = is_block_device(raw_file.as_raw_fd());
let alignment = raw_file.alignment() as u64;
Ok(RawAio {
raw_file,
data_io,
alignment,
is_block_device,
})
}
}
impl AsyncIo for RawAio {
fn notifier(&self) -> &EventFd {
self.data_io.notifier()
}
fn alignment(&self) -> u64 {
self.alignment
}
fn submit_data_operation(&mut self, mut op: AsyncIoOperation) -> AsyncIoResult<()> {
let is_read = op.is_read();
if operation_is_aligned(&op, self.alignment) {
let fd = self.raw_file.as_raw_fd();
return self.data_io.submit_operation(fd, op).map_err(|e| {
if is_read {
AsyncIoError::ReadVectored(e)
} else {
AsyncIoError::WriteVectored(e)
}
});
}
let result = run_unaligned_operation(&self.raw_file, &mut op)?;
self.data_io
.inject_completion(AsyncIoCompletion::from_operation(op, result));
Ok(())
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
let fd = self.raw_file.as_raw_fd();
if let Some(user_data) = user_data {
self.data_io
.submit_fsync(fd, user_data)
.map_err(AsyncIoError::Fsync)?;
} else {
// SAFETY: FFI call with a valid fd
unsafe { libc::fsync(fd) };
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
self.data_io.next_completion()
}
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
// Linux AIO has no IOCB command for fallocate, so perform the
// operation synchronously and signal completion via the completion
// list, matching the pattern used by the sync backend (RawSync).
punch_hole(
self.raw_file.as_raw_fd(),
self.is_block_device,
offset,
length,
)
.map_err(AsyncIoError::PunchHole)?;
self.data_io
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
Ok(())
}
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
// Same as punch_hole().
write_zeroes(
self.raw_file.as_raw_fd(),
self.is_block_device,
offset,
length,
)
.map_err(AsyncIoError::WriteZeroes)?;
self.data_io
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
Ok(())
}
}
#[cfg(test)]
mod unit_tests {
use vmm_sys_util::tempfile::TempFile;
use super::*;
use crate::formats::raw::tests;
#[test]
fn test_punch_hole() {
let temp_file = TempFile::new().unwrap();
let mut file = temp_file.into_file();
let mut async_io =
RawAio::new(AlignedFile::new(file.try_clone().unwrap(), false), 128).unwrap();
tests::test_punch_hole(&mut async_io, &mut file);
}
#[test]
fn test_write_zeroes() {
let temp_file = TempFile::new().unwrap();
let mut file = temp_file.into_file();
let mut async_io =
RawAio::new(AlignedFile::new(file.try_clone().unwrap(), false), 128).unwrap();
tests::test_write_zeroes(&mut async_io, &mut file);
}
#[test]
fn test_punch_hole_multiple_operations() {
let temp_file = TempFile::new().unwrap();
let mut file = temp_file.into_file();
let mut async_io =
RawAio::new(AlignedFile::new(file.try_clone().unwrap(), false), 128).unwrap();
tests::test_punch_hole_multiple_operations(&mut async_io, &mut file);
}
}

View File

@@ -0,0 +1,181 @@
// Copyright © 2021 Intel Corporation
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::collections::VecDeque;
use std::io;
use std::os::unix::io::AsRawFd;
use vmm_sys_util::eventfd::EventFd;
use super::{operation_is_aligned, run_unaligned_operation};
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
use crate::sparse::{punch_hole, write_zeroes};
use crate::{AlignedFile, is_block_device};
pub(crate) struct RawSync {
raw_file: AlignedFile,
eventfd: EventFd,
completion_list: VecDeque<AsyncIoCompletion>,
alignment: u64,
is_block_device: bool,
}
impl RawSync {
pub(crate) fn new(raw_file: AlignedFile) -> Self {
let is_block_device = is_block_device(raw_file.as_raw_fd());
let alignment = raw_file.alignment() as u64;
RawSync {
raw_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
completion_list: VecDeque::new(),
alignment,
is_block_device,
}
}
}
impl AsyncIo for RawSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
}
fn alignment(&self) -> u64 {
self.alignment
}
fn submit_data_operation(&mut self, mut op: AsyncIoOperation) -> AsyncIoResult<()> {
let is_read = op.is_read();
let result = if operation_is_aligned(&op, self.alignment) {
let fd = self.raw_file.as_raw_fd();
let offset = op.offset();
let iovecs = op.iovecs();
let result = if is_read {
// SAFETY: the memory pointed to by `iovecs` is backed by the op,
// and valid for the kernel to write to by construction of
// AsyncIoOperation.
unsafe {
libc::preadv(
fd as libc::c_int,
iovecs.as_ptr(),
iovecs.len() as libc::c_int,
offset,
)
}
} else {
// SAFETY: the memory pointed to by `iovecs` is backed by the op,
// and valid for the kernel to read from by construction of
// AsyncIoOperation.
unsafe {
libc::pwritev(
fd as libc::c_int,
iovecs.as_ptr(),
iovecs.len() as libc::c_int,
offset,
)
}
};
if result < 0 {
let error = io::Error::last_os_error();
return Err(if is_read {
AsyncIoError::ReadVectored(error)
} else {
AsyncIoError::WriteVectored(error)
});
}
result as i32
} else {
run_unaligned_operation(&self.raw_file, &mut op)?
};
self.completion_list
.push_back(AsyncIoCompletion::from_operation(op, result));
self.eventfd.write(1).unwrap();
Ok(())
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
// SAFETY: FFI call
let result = unsafe { libc::fsync(self.raw_file.as_raw_fd() as libc::c_int) };
if result < 0 {
return Err(AsyncIoError::Fsync(io::Error::last_os_error()));
}
if let Some(user_data) = user_data {
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, result, None));
self.eventfd.write(1).unwrap();
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
self.completion_list.pop_front()
}
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
punch_hole(
self.raw_file.as_raw_fd(),
self.is_block_device,
offset,
length,
)
.map_err(AsyncIoError::PunchHole)?;
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, 0, None));
self.eventfd.write(1).unwrap();
Ok(())
}
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
write_zeroes(
self.raw_file.as_raw_fd(),
self.is_block_device,
offset,
length,
)
.map_err(AsyncIoError::WriteZeroes)?;
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, 0, None));
self.eventfd.write(1).unwrap();
Ok(())
}
}
#[cfg(test)]
mod unit_tests {
use vmm_sys_util::tempfile::TempFile;
use super::*;
use crate::formats::raw::tests;
#[test]
fn test_punch_hole() {
let temp_file = TempFile::new().unwrap();
let mut file = temp_file.into_file();
let mut async_io = RawSync::new(AlignedFile::new(file.try_clone().unwrap(), false));
tests::test_punch_hole(&mut async_io, &mut file);
}
#[test]
fn test_write_zeroes() {
let temp_file = TempFile::new().unwrap();
let mut file = temp_file.into_file();
let mut async_io = RawSync::new(AlignedFile::new(file.try_clone().unwrap(), false));
tests::test_write_zeroes(&mut async_io, &mut file);
}
#[test]
fn test_punch_hole_multiple_operations() {
let temp_file = TempFile::new().unwrap();
let mut file = temp_file.into_file();
let mut async_io = RawSync::new(AlignedFile::new(file.try_clone().unwrap(), false));
tests::test_punch_hole_multiple_operations(&mut async_io, &mut file);
}
}

View File

@@ -0,0 +1,162 @@
// Copyright © 2021 Intel Corporation
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::os::unix::io::AsRawFd;
use libc::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE};
use vmm_sys_util::eventfd::EventFd;
use super::{operation_is_aligned, run_unaligned_operation};
use crate::async_io::{
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, UringDataIo,
};
use crate::error::{BlockError, BlockErrorKind, BlockResult};
use crate::sparse::{blkdiscard, blkzeroout};
use crate::{AlignedFile, is_block_device};
pub(crate) struct RawAsync {
raw_file: AlignedFile,
data_io: UringDataIo,
alignment: u64,
is_block_device: bool,
}
impl RawAsync {
pub(crate) fn new(raw_file: AlignedFile, ring_depth: u32) -> BlockResult<Self> {
let data_io =
UringDataIo::new(ring_depth).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
let is_block_device = is_block_device(raw_file.as_raw_fd());
let alignment = raw_file.alignment() as u64;
Ok(RawAsync {
raw_file,
data_io,
alignment,
is_block_device,
})
}
}
impl AsyncIo for RawAsync {
fn notifier(&self) -> &EventFd {
self.data_io.notifier()
}
fn alignment(&self) -> u64 {
self.alignment
}
fn submit_data_operation(&mut self, mut op: AsyncIoOperation) -> AsyncIoResult<()> {
let is_read = op.is_read();
if operation_is_aligned(&op, self.alignment) {
let fd = self.raw_file.as_raw_fd();
return self.data_io.submit_operation(fd, op).map_err(|e| {
if is_read {
AsyncIoError::ReadVectored(e)
} else {
AsyncIoError::WriteVectored(e)
}
});
}
let result = run_unaligned_operation(&self.raw_file, &mut op)?;
self.data_io
.inject_completion(AsyncIoCompletion::from_operation(op, result));
Ok(())
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
let fd = self.raw_file.as_raw_fd();
if let Some(user_data) = user_data {
self.data_io
.submit_fsync(fd, user_data)
.map_err(AsyncIoError::Fsync)?;
} else {
// SAFETY: FFI call with a valid fd
unsafe { libc::fsync(fd) };
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
self.data_io.next_completion()
}
fn batch_requests_enabled(&self) -> bool {
true
}
fn submit_batch_requests(&mut self, batch_request: Vec<AsyncIoOperation>) -> AsyncIoResult<()> {
if self.alignment != 0 {
let mut aligned_batch = Vec::with_capacity(batch_request.len());
for mut op in batch_request {
if operation_is_aligned(&op, self.alignment) {
aligned_batch.push(op);
} else {
let result = run_unaligned_operation(&self.raw_file, &mut op)?;
self.data_io
.inject_completion(AsyncIoCompletion::from_operation(op, result));
}
}
if aligned_batch.is_empty() {
return Ok(());
}
return self
.data_io
.submit_batch(self.raw_file.as_raw_fd(), aligned_batch)
.map_err(AsyncIoError::SubmitBatchRequests);
}
self.data_io
.submit_batch(self.raw_file.as_raw_fd(), batch_request)
.map_err(AsyncIoError::SubmitBatchRequests)
}
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
// Some block devices don't support fallocate(). Use ioctl instead. The assumption is that
// this happens rarely and we don't need to introduce unnecessary complexity by submitting
// a fallocate request, reaping ENOTSUPP in the completion routine, and reissuing the
// request with an ioctl.
if self.is_block_device {
blkdiscard(self.raw_file.as_raw_fd(), offset, length)
.map_err(AsyncIoError::PunchHole)?;
// Deliver the completion through the normal io_uring path by
// queuing a NOP carrying `user_data`. The registered eventfd will
// fire when it completes, just like any other request.
return self
.data_io
.submit_nop(user_data)
.map_err(AsyncIoError::PunchHole);
}
let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
self.data_io
.submit_fallocate(self.raw_file.as_raw_fd(), offset, length, mode, user_data)
.map_err(AsyncIoError::PunchHole)
}
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
// Same rationale as punch_hole().
if self.is_block_device {
blkzeroout(self.raw_file.as_raw_fd(), offset, length)
.map_err(AsyncIoError::WriteZeroes)?;
return self
.data_io
.submit_nop(user_data)
.map_err(AsyncIoError::WriteZeroes);
}
let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
self.data_io
.submit_fallocate(self.raw_file.as_raw_fd(), offset, length, mode, user_data)
.map_err(AsyncIoError::WriteZeroes)
}
}

View File

@@ -0,0 +1,319 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//! Raw disk image format.
//!
//! Provides [`RawDisk`], the `DiskFile` wrapper for flat disk images
//! with no metadata or copy on write layer.
use std::fs::File;
use std::io;
use std::os::unix::fs::FileTypeExt;
use std::os::unix::io::AsRawFd;
use log::warn;
use self::engine_aio::RawAio;
use self::engine_sync::RawSync;
#[cfg(feature = "io_uring")]
use self::engine_uring::RawAsync;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoOperation, AsyncIoResult, BorrowedDiskFd, DiskFileError,
};
use crate::error::{BlockError, BlockErrorKind, BlockResult};
use crate::{AlignedFile, DiskTopology, disk_file, probe_sparse_support, query_device_size};
mod engine_aio;
pub(crate) mod engine_sync;
#[cfg(feature = "io_uring")]
pub(crate) mod engine_uring;
#[cfg(test)]
mod tests;
/// Selects which async I/O backend a `RawDisk` uses.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RawBackend {
/// Blocking I/O where the caller waits for completion.
Sync,
/// Modern asynchronous I/O using shared submission and completion
/// rings for lower overhead operation dispatch and completion handling.
#[cfg(feature = "io_uring")]
IoUring,
/// Legacy asynchronous I/O where requests are handed to the kernel
/// and completions are collected later.
Aio,
}
/// Unified DiskFile wrapper for raw disk images.
///
/// Owns the underlying file and delegates async I/O creation to the
/// backend selected at construction time via [`RawBackend`].
#[derive(Debug)]
pub struct RawDisk {
file: File,
backend: RawBackend,
direct: bool,
}
impl RawDisk {
pub fn new(file: File, backend: RawBackend, direct: bool) -> Self {
Self {
file,
backend,
direct,
}
}
}
impl disk_file::DiskSize for RawDisk {
fn logical_size(&self) -> BlockResult<u64> {
query_device_size(&self.file)
.map(|(logical_size, _)| logical_size)
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e)))
}
}
impl disk_file::PhysicalSize for RawDisk {
fn physical_size(&self) -> BlockResult<u64> {
query_device_size(&self.file)
.map(|(_, physical_size)| physical_size)
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e)))
}
}
impl disk_file::DiskFd for RawDisk {
fn fd(&self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.file.as_raw_fd())
}
}
impl disk_file::Geometry for RawDisk {
fn topology(&self) -> DiskTopology {
DiskTopology::probe(&self.file).unwrap_or_else(|_| {
warn!("Unable to get device topology. Using default topology");
DiskTopology::default()
})
}
}
impl disk_file::SparseCapable for RawDisk {
fn supports_sparse_operations(&self) -> bool {
probe_sparse_support(&self.file)
}
}
impl disk_file::Resizable for RawDisk {
fn resize(&mut self, size: u64) -> BlockResult<()> {
let fd_metadata = self
.file
.metadata()
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)))?;
if fd_metadata.file_type().is_block_device() {
// Block devices cannot be resized via ftruncate; they are resized
// externally (LVM, losetup, etc.). Verify the size matches.
let (actual_size, _) = query_device_size(&self.file)
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)))?;
if actual_size != size {
return Err(BlockError::new(
BlockErrorKind::Io,
DiskFileError::ResizeError(io::Error::other(format!(
"Block device size {actual_size} does not match requested size {size}"
))),
));
}
Ok(())
} else {
self.file
.set_len(size)
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)))
}
}
}
impl disk_file::DiskFile for RawDisk {}
impl disk_file::AsyncDiskFile for RawDisk {
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
let file = self
.file
.try_clone()
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?;
Ok(Box::new(RawDisk {
file,
backend: self.backend,
direct: self.direct,
}))
}
fn create_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
let file = self
.file
.try_clone()
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?;
let raw_file = AlignedFile::new(file, self.direct);
match self.backend {
RawBackend::Sync => Ok(Box::new(RawSync::new(raw_file))),
#[cfg(feature = "io_uring")]
RawBackend::IoUring => Ok(Box::new(RawAsync::new(raw_file, ring_depth)?)),
RawBackend::Aio => Ok(Box::new(RawAio::new(raw_file, ring_depth)?)),
}
}
}
/// True when `op` satisfies `alignment` and can go straight to the kernel.
fn operation_is_aligned(op: &AsyncIoOperation, alignment: u64) -> bool {
if alignment == 0 {
return true;
}
if !(op.offset() as u64).is_multiple_of(alignment) {
return false;
}
op.iovecs().iter().all(|iov| {
(iov.iov_base as u64).is_multiple_of(alignment)
&& (iov.iov_len as u64).is_multiple_of(alignment)
})
}
/// Runs an unaligned O_DIRECT operation synchronously through `aligned_file`.
fn run_unaligned_operation(
aligned_file: &AlignedFile,
op: &mut AsyncIoOperation,
) -> AsyncIoResult<i32> {
let offset = op.offset() as u64;
let total_len = op.total_len();
if op.is_read() {
let n = aligned_file
.read_unaligned(offset, total_len, |data| op.write_bytes_at(0, data))
.map_err(AsyncIoError::ReadVectored)?;
Ok(n as i32)
} else {
let n = aligned_file
.write_unaligned(offset, total_len, |data| op.read_bytes_at(0, data))
.map_err(AsyncIoError::WriteVectored)?;
Ok(n as i32)
}
}
#[cfg(test)]
mod unit_tests {
use std::fs::File;
use vmm_sys_util::tempfile::TempFile;
use super::*;
use crate::async_io::AsyncIo;
use crate::disk_file::{AsyncDiskFile, DiskSize, PhysicalSize, Resizable};
const TEST_SIZE: u64 = 0x1122_3344;
fn make_raw_file() -> File {
let file: File = TempFile::new().unwrap().into_file();
file.set_len(TEST_SIZE).unwrap();
file
}
#[test]
fn new_sync_returns_correct_size() {
let file = make_raw_file();
let disk = RawDisk::new(file, RawBackend::Sync, false);
assert_eq!(disk.logical_size().unwrap(), TEST_SIZE);
}
fn assert_async_io_from_dyn(disk: &dyn AsyncDiskFile, expect_backend: RawBackend) {
let io: Box<dyn AsyncIo> = disk.create_async_io(128).unwrap();
cfg_if::cfg_if! {
if #[cfg(feature = "io_uring")] {
let expected_batch_requests = expect_backend == RawBackend::IoUring;
} else {
let _ = expect_backend;
let expected_batch_requests = false;
}
}
assert_eq!(io.batch_requests_enabled(), expected_batch_requests);
}
fn assert_sync_backend(disk: &RawDisk) {
assert_eq!(disk.backend, RawBackend::Sync);
assert_async_io_from_dyn(disk, RawBackend::Sync);
}
fn assert_aio_backend(disk: &RawDisk) {
assert_eq!(disk.backend, RawBackend::Aio);
assert_async_io_from_dyn(disk, RawBackend::Aio);
}
#[cfg(feature = "io_uring")]
fn assert_io_uring_backend(disk: &RawDisk) {
assert_eq!(disk.backend, RawBackend::IoUring);
assert_async_io_from_dyn(disk, RawBackend::IoUring);
}
#[test]
fn sync_backend_disables_batch_requests() {
let file = make_raw_file();
let disk = RawDisk::new(file, RawBackend::Sync, false);
assert_sync_backend(&disk);
}
#[test]
fn aio_backend_disables_batch_requests() {
let file = make_raw_file();
let disk = RawDisk::new(file, RawBackend::Aio, false);
assert_aio_backend(&disk);
}
#[cfg(feature = "io_uring")]
#[test]
fn io_uring_backend_enables_batch_requests() {
let file = make_raw_file();
let disk = RawDisk::new(file, RawBackend::IoUring, false);
assert_io_uring_backend(&disk);
}
fn assert_try_clone(disk: &RawDisk, expect_backend: RawBackend) {
let cloned = disk.try_clone().unwrap();
assert_async_io_from_dyn(cloned.as_ref(), expect_backend);
}
#[test]
fn try_clone_preserves_sync_backend() {
let file = make_raw_file();
let disk = RawDisk::new(file, RawBackend::Sync, false);
assert_try_clone(&disk, RawBackend::Sync);
}
#[test]
fn try_clone_preserves_aio_backend() {
let file = make_raw_file();
let disk = RawDisk::new(file, RawBackend::Aio, false);
assert_try_clone(&disk, RawBackend::Aio);
}
#[cfg(feature = "io_uring")]
#[test]
fn try_clone_preserves_io_uring_backend() {
let file = make_raw_file();
let disk = RawDisk::new(file, RawBackend::IoUring, false);
assert_try_clone(&disk, RawBackend::IoUring);
}
#[test]
fn resize_changes_file_size() {
let file = make_raw_file();
let mut disk = RawDisk::new(file, RawBackend::Aio, false);
let new_size = TEST_SIZE * 2;
disk.resize(new_size).unwrap();
assert_eq!(disk.logical_size().unwrap(), new_size);
}
#[test]
fn physical_size_reports_allocated_blocks() {
let file = make_raw_file();
let disk = RawDisk::new(file, RawBackend::Aio, false);
// Sparse file: physical size is less than logical size.
assert!(disk.physical_size().unwrap() < disk.logical_size().unwrap());
}
}

View File

@@ -0,0 +1,169 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//! Shared test helpers for [`AsyncIo`] backends.
//!
//! Each helper takes a `&mut dyn AsyncIo` together with the [`File`] handle
//! that backs the I/O object, so the same logic exercises every backend with
//! only the constructor differing.
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use crate::async_io::{AsyncIo, AsyncIoError};
fn next_completion(async_io: &mut dyn AsyncIo) -> (u64, i32) {
let completion = async_io.next_completed_request().expect("No completion");
(completion.user_data, completion.result)
}
/// Tests punching a hole in the middle of a 4 MB file and verifying data
/// integrity around the hole.
pub fn test_punch_hole(async_io: &mut dyn AsyncIo, file: &mut File) {
// Write 4MB of data
let data = vec![0xAA; 4 * 1024 * 1024];
file.write_all(&data).unwrap();
file.sync_all().unwrap();
// Punch hole in the middle (1MB at offset 1MB)
let offset = 1024 * 1024;
let length = 1024 * 1024;
async_io.punch_hole(offset, length, 1).unwrap();
// Check completion
let (user_data, result) = next_completion(async_io);
assert_eq!(user_data, 1);
assert_eq!(result, 0);
// Verify the hole reads as zeros
file.seek(SeekFrom::Start(offset)).unwrap();
let mut read_buf = vec![0; length as usize];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0),
"Punched hole should read as zeros"
);
// Verify data before hole is intact
file.seek(SeekFrom::Start(0)).unwrap();
let mut read_buf = vec![0; 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0xAA),
"Data before hole should be intact"
);
// Verify data after hole is intact
file.seek(SeekFrom::Start(offset + length)).unwrap();
let mut read_buf = vec![0; 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0xAA),
"Data after hole should be intact"
);
}
/// Tests writing zeroes to a 512 KB region inside a 4 MB file and verifying
/// surrounding data is preserved. Gracefully skips when the filesystem does
/// not support `FALLOC_FL_ZERO_RANGE`.
pub fn test_write_zeroes(async_io: &mut dyn AsyncIo, file: &mut File) {
// Write 4MB of data
let data = vec![0xBB; 4 * 1024 * 1024];
file.write_all(&data).unwrap();
file.sync_all().unwrap();
// Write zeros in the middle (512KB at offset 2MB)
let offset = 2 * 1024 * 1024;
let length = 512 * 1024;
let write_zeroes_result = async_io.write_zeroes(offset, length, 2);
// FALLOC_FL_ZERO_RANGE might not be supported on all filesystems (e.g., tmpfs)
// If it fails with ENOTSUP, skip the test
if let Err(AsyncIoError::WriteZeroes(ref e)) = write_zeroes_result
&& (e.raw_os_error() == Some(libc::EOPNOTSUPP) || e.raw_os_error() == Some(libc::ENOTSUP))
{
eprintln!("Skipping test_write_zeroes: filesystem doesn't support FALLOC_FL_ZERO_RANGE");
return;
}
write_zeroes_result.unwrap();
// Check completion
let (user_data, result) = next_completion(async_io);
assert_eq!(user_data, 2);
assert_eq!(result, 0);
// Verify the zeroed region reads as zeros
file.seek(SeekFrom::Start(offset)).unwrap();
let mut read_buf = vec![0; length as usize];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0),
"Zeroed region should read as zeros"
);
// Verify data before zeroed region is intact
file.seek(SeekFrom::Start(offset - 1024)).unwrap();
let mut read_buf = vec![0; 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0xBB),
"Data before zeroed region should be intact"
);
// Verify data after zeroed region is intact
file.seek(SeekFrom::Start(offset + length)).unwrap();
let mut read_buf = vec![0; 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(
read_buf.iter().all(|&b| b == 0xBB),
"Data after zeroed region should be intact"
);
}
/// Tests punching multiple holes in an 8 MB file and verifying each hole
/// independently reads as zeroes.
pub fn test_punch_hole_multiple_operations(async_io: &mut dyn AsyncIo, file: &mut File) {
// Write 8MB of data
let data = vec![0xCC; 8 * 1024 * 1024];
file.write_all(&data).unwrap();
file.sync_all().unwrap();
// Punch multiple holes
async_io.punch_hole(1024 * 1024, 512 * 1024, 10).unwrap();
async_io
.punch_hole(3 * 1024 * 1024, 512 * 1024, 11)
.unwrap();
async_io
.punch_hole(5 * 1024 * 1024, 512 * 1024, 12)
.unwrap();
// Check all completions
let (user_data, result) = next_completion(async_io);
assert_eq!(user_data, 10);
assert_eq!(result, 0);
let (user_data, result) = next_completion(async_io);
assert_eq!(user_data, 11);
assert_eq!(result, 0);
let (user_data, result) = next_completion(async_io);
assert_eq!(user_data, 12);
assert_eq!(result, 0);
// Verify all holes read as zeros
file.seek(SeekFrom::Start(1024 * 1024)).unwrap();
let mut read_buf = vec![0; 512 * 1024];
file.read_exact(&mut read_buf).unwrap();
assert!(read_buf.iter().all(|&b| b == 0));
file.seek(SeekFrom::Start(3 * 1024 * 1024)).unwrap();
file.read_exact(&mut read_buf).unwrap();
assert!(read_buf.iter().all(|&b| b == 0));
file.seek(SeekFrom::Start(5 * 1024 * 1024)).unwrap();
file.read_exact(&mut read_buf).unwrap();
assert!(read_buf.iter().all(|&b| b == 0));
}

View File

@@ -0,0 +1,58 @@
// Copyright © 2021 Intel Corporation
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0
use std::io;
use vmm_sys_util::eventfd::EventFd;
use crate::AlignedFile;
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
use crate::formats::raw::engine_sync::RawSync;
pub(super) struct FixedVhdSync {
raw_file_sync: RawSync,
size: u64,
}
impl FixedVhdSync {
pub(super) fn new(raw_file: AlignedFile, size: u64) -> Self {
FixedVhdSync {
raw_file_sync: RawSync::new(raw_file),
size,
}
}
}
impl AsyncIo for FixedVhdSync {
fn notifier(&self) -> &EventFd {
self.raw_file_sync.notifier()
}
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
op.validate_bounds(self.size)?;
self.raw_file_sync.submit_data_operation(op)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.raw_file_sync.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
self.raw_file_sync.next_completed_request()
}
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
Err(AsyncIoError::PunchHole(io::Error::other(
"punch_hole not supported for fixed VHD",
)))
}
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
Err(AsyncIoError::WriteZeroes(io::Error::other(
"write_zeroes not supported for fixed VHD",
)))
}
}

View File

@@ -0,0 +1,73 @@
// Copyright © 2021 Intel Corporation
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0
use std::io;
use vmm_sys_util::eventfd::EventFd;
use crate::AlignedFile;
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
use crate::error::BlockResult;
use crate::formats::raw::engine_uring::RawAsync;
pub(super) struct FixedVhdAsync {
raw_file_async: RawAsync,
size: u64,
}
impl FixedVhdAsync {
pub(super) fn new(raw_file: AlignedFile, ring_depth: u32, size: u64) -> BlockResult<Self> {
let raw_file_async = RawAsync::new(raw_file, ring_depth)?;
Ok(FixedVhdAsync {
raw_file_async,
size,
})
}
}
impl AsyncIo for FixedVhdAsync {
fn notifier(&self) -> &EventFd {
self.raw_file_async.notifier()
}
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
op.validate_bounds(self.size)?;
self.raw_file_async.submit_data_operation(op)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.raw_file_async.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
self.raw_file_async.next_completed_request()
}
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
Err(AsyncIoError::PunchHole(io::Error::other(
"punch_hole not supported for fixed VHD",
)))
}
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
Err(AsyncIoError::WriteZeroes(io::Error::other(
"write_zeroes not supported for fixed VHD",
)))
}
fn batch_requests_enabled(&self) -> bool {
true
}
fn submit_batch_requests(&mut self, batch_request: Vec<AsyncIoOperation>) -> AsyncIoResult<()> {
for op in &batch_request {
op.validate_bounds(self.size)?;
}
self.raw_file_async.submit_batch_requests(batch_request)
}
}

View File

@@ -0,0 +1,59 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use super::footer::VhdFooter;
#[derive(Debug)]
pub(super) struct FixedVhd {
file: File,
size: u64,
}
impl FixedVhd {
pub(super) fn new(mut file: File) -> io::Result<Self> {
let footer = VhdFooter::new(&mut file)?;
Ok(Self {
file,
size: footer.current_size(),
})
}
pub(crate) fn file(&self) -> &File {
&self.file
}
}
impl AsRawFd for FixedVhd {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl FixedVhd {
pub(crate) fn logical_size(&self) -> Result<u64, crate::Error> {
Ok(self.size)
}
/// Returns the physical size of the underlying file.
pub(crate) fn physical_size(&self) -> Result<u64, crate::Error> {
self.file
.metadata()
.map(|m| m.len())
.map_err(crate::Error::GetFileMetadata)
}
}
impl Clone for FixedVhd {
fn clone(&self) -> Self {
Self {
file: self.file.try_clone().expect("FixedVhd cloning failed"),
size: self.size,
}
}
}

View File

@@ -3,12 +3,17 @@
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::io;
use std::os::unix::fs::FileExt;
use crate::{DiskTopology, read_aligned_block_size};
use crate::{AlignedFile, query_device_size};
// Production code uses: cookie, file_format_version, data_offset,
// current_size, disk_type. The remaining fields are parsed for VHD
// spec completeness and exercised only by unit tests.
#[derive(Clone, Copy)]
pub struct VhdFooter {
#[cfg_attr(not(test), expect(dead_code))]
pub(super) struct VhdFooter {
cookie: u64,
features: u32,
file_format_version: u32,
@@ -27,17 +32,14 @@ pub struct VhdFooter {
}
impl VhdFooter {
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
let blocksize = DiskTopology::probe(file)?.logical_block_size as usize;
// Place the cursor in the last block of the file
file.seek(SeekFrom::End(0 - (blocksize as i64)))?;
// Read in the last block
let data = read_aligned_block_size(file)?;
// We only care about the last sector
let offset = blocksize - 512;
let sector = &data[offset..];
pub(super) fn new(file: &mut File) -> io::Result<VhdFooter> {
let aligned = AlignedFile::new(file.try_clone()?, true);
let size = query_device_size(file)?.0;
let footer_offset = size.checked_sub(512).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "file too small for VHD footer")
})?;
let mut sector = [0u8; 512];
aligned.read_exact_at(&mut sector, footer_offset)?;
Ok(VhdFooter {
cookie: u64::from_be_bytes(sector[0..8].try_into().unwrap()),
@@ -58,55 +60,65 @@ impl VhdFooter {
})
}
pub fn cookie(&self) -> u64 {
pub(super) fn cookie(&self) -> u64 {
self.cookie
}
#[cfg(test)]
pub fn features(&self) -> u32 {
self.features
}
pub fn file_format_version(&self) -> u32 {
pub(super) fn file_format_version(&self) -> u32 {
self.file_format_version
}
pub fn data_offset(&self) -> u64 {
pub(super) fn data_offset(&self) -> u64 {
self.data_offset
}
#[cfg(test)]
pub fn time_stamp(&self) -> u32 {
self.time_stamp
}
#[cfg(test)]
pub fn creator_application(&self) -> u32 {
self.creator_application
}
#[cfg(test)]
pub fn creator_version(&self) -> u32 {
self.creator_version
}
#[cfg(test)]
pub fn creator_host_os(&self) -> u32 {
self.creator_host_os
}
#[cfg(test)]
pub fn original_size(&self) -> u64 {
self.original_size
}
pub fn current_size(&self) -> u64 {
pub(super) fn current_size(&self) -> u64 {
self.current_size
}
#[cfg(test)]
pub fn disk_geometry(&self) -> u32 {
self.disk_geometry
}
pub fn disk_type(&self) -> u32 {
pub(super) fn disk_type(&self) -> u32 {
self.disk_type
}
#[cfg(test)]
pub fn checksum(&self) -> u32 {
self.checksum
}
#[cfg(test)]
pub fn unique_id(&self) -> u128 {
self.unique_id
}
#[cfg(test)]
pub fn saved_state(&self) -> u8 {
self.saved_state
}
}
/// Determine image type through file parsing.
pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
pub fn is_fixed_vhd(f: &mut File) -> io::Result<bool> {
let footer = VhdFooter::new(f)?;
// "conectix" => 0x636f6e6563746978

View File

@@ -0,0 +1,332 @@
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//! Fixed VHD disk image format.
//!
//! Provides [`VhdDisk`], the `DiskFile` wrapper for fixed size VHD
//! images.
mod engine_sync;
#[cfg(feature = "io_uring")]
mod engine_uring;
mod fixed;
mod footer;
use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;
pub use footer::is_fixed_vhd;
use log::warn;
use self::engine_sync::FixedVhdSync;
#[cfg(feature = "io_uring")]
use self::engine_uring::FixedVhdAsync;
use self::fixed::FixedVhd;
use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError};
use crate::disk_file::DiskSize;
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
use crate::{AlignedFile, DiskTopology, Error, disk_file};
#[derive(Debug)]
pub struct VhdDisk {
inner: FixedVhd,
use_io_uring: bool,
direct: bool,
}
impl VhdDisk {
pub fn new(file: File, use_io_uring: bool, direct: bool) -> BlockResult<Self> {
#[cfg(not(feature = "io_uring"))]
if use_io_uring {
return Err(BlockError::new(
BlockErrorKind::UnsupportedFeature,
DiskFileError::NewAsyncIo(io::Error::other(
"io_uring requested but feature is not enabled",
)),
));
}
Ok(Self {
inner: FixedVhd::new(file).map_err(|e| BlockError::from(e).with_op(ErrorOp::Open))?,
use_io_uring,
direct,
})
}
}
impl disk_file::DiskSize for VhdDisk {
fn logical_size(&self) -> BlockResult<u64> {
self.inner
.logical_size()
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))
}
}
impl disk_file::PhysicalSize for VhdDisk {
fn physical_size(&self) -> BlockResult<u64> {
self.inner.physical_size().map_err(|e| match e {
Error::GetFileMetadata(io) => {
BlockError::new(BlockErrorKind::Io, Error::GetFileMetadata(io))
}
_ => unreachable!("unexpected error from FixedVhd::physical_size(): {e}"),
})
}
}
impl disk_file::DiskFd for VhdDisk {
fn fd(&self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.inner.as_raw_fd())
}
}
impl disk_file::Geometry for VhdDisk {
fn topology(&self) -> DiskTopology {
DiskTopology::probe(self.inner.file()).unwrap_or_else(|_| {
warn!("Unable to get device topology. Using default topology");
DiskTopology::default()
})
}
}
impl disk_file::SparseCapable for VhdDisk {}
impl disk_file::Resizable for VhdDisk {
fn resize(&mut self, _size: u64) -> BlockResult<()> {
Err(BlockError::new(
BlockErrorKind::UnsupportedFeature,
DiskFileError::ResizeError(io::Error::other("resize not supported for fixed VHD")),
)
.with_op(ErrorOp::Resize))
}
}
impl disk_file::DiskFile for VhdDisk {}
impl disk_file::AsyncDiskFile for VhdDisk {
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
Ok(Box::new(VhdDisk {
inner: self.inner.clone(),
use_io_uring: self.use_io_uring,
direct: self.direct,
}))
}
fn create_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
let size = self.logical_size()?;
let file = self.inner.file().try_clone().map_err(|e| {
BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)).with_op(ErrorOp::Open)
})?;
let raw_file = AlignedFile::new(file, self.direct);
if self.use_io_uring {
#[cfg(feature = "io_uring")]
{
return Ok(Box::new(FixedVhdAsync::new(raw_file, ring_depth, size)?));
}
#[cfg(not(feature = "io_uring"))]
unreachable!("use_io_uring is set but io_uring feature is not enabled");
}
let _ = ring_depth;
Ok(Box::new(FixedVhdSync::new(raw_file, size)))
}
}
#[cfg(test)]
mod unit_tests {
use std::fs::File;
use std::io::{Seek, SeekFrom, Write};
use vmm_sys_util::tempfile::TempFile;
use super::*;
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoOperation, OwnedIoBuffer};
use crate::disk_file::{AsyncDiskFile, DiskSize, PhysicalSize, Resizable};
/// Minimal fixed VHD footer (disk type = 2, current_size = 0x11223344).
fn fixed_vhd_footer() -> &'static [u8] {
&[
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
0x00, 0x00, 0x00, 0x02, // features
0x00, 0x01, 0x00, 0x00, // file format version
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // data offset
0x27, 0xa6, 0xa6, 0x5d, // time stamp
0x71, 0x65, 0x6d, 0x75, // creator application
0x00, 0x05, 0x00, 0x03, // creator version
0x57, 0x69, 0x32, 0x6b, // creator host os
0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, // original size
0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, // current size
0x11, 0xe0, 0x10, 0x3f, // disk geometry
0x00, 0x00, 0x00, 0x02, // disk type
0x00, 0x00, 0x00, 0x00, // checksum
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, // unique id
0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b, 0xf2, 0x23, 0x00, // saved state
]
}
fn make_vhd_file() -> File {
let mut file: File = TempFile::new().unwrap().into_file();
let data_size: u64 = 0x1122_3344;
file.set_len(data_size + 0x200).unwrap();
file.seek(SeekFrom::Start(data_size)).unwrap();
file.write_all(fixed_vhd_footer()).unwrap();
file
}
#[test]
fn new_sync_returns_correct_size() {
let file = make_vhd_file();
let disk = VhdDisk::new(file, false, false).unwrap();
assert_eq!(disk.logical_size().unwrap(), 0x1122_3344);
}
fn assert_async_io_from_dyn(disk: &dyn AsyncDiskFile, expect_batch: bool) {
let io: Box<dyn AsyncIo> = disk.create_async_io(128).unwrap();
assert_eq!(io.batch_requests_enabled(), expect_batch);
}
fn assert_async_io(disk: &VhdDisk, expect_batch: bool) {
assert_async_io_from_dyn(disk, expect_batch);
}
#[test]
fn sync_backend_disables_batch_requests() {
let file = make_vhd_file();
let disk = VhdDisk::new(file, false, false).unwrap();
assert_async_io(&disk, false);
}
#[cfg(feature = "io_uring")]
#[test]
fn io_uring_backend_enables_batch_requests() {
let file = make_vhd_file();
let disk = VhdDisk::new(file, true, false).unwrap();
assert_async_io(&disk, true);
}
#[test]
fn sync_rejects_read_straddling_logical_size() {
let file = TempFile::new().unwrap().into_file();
file.set_len(0x2000).unwrap();
let mut sync_io =
FixedVhdSync::new(AlignedFile::new(file.try_clone().unwrap(), false), 0x1000);
let op = AsyncIoOperation::read_to_vec(0x800, OwnedIoBuffer::from_vec(vec![0; 0x900]), 1);
assert!(matches!(
sync_io.submit_data_operation(op),
Err(AsyncIoError::ReadVectored(_))
));
}
#[test]
fn sync_rejects_write_straddling_logical_size() {
let file = TempFile::new().unwrap().into_file();
file.set_len(0x2000).unwrap();
let mut sync_io =
FixedVhdSync::new(AlignedFile::new(file.try_clone().unwrap(), false), 0x1000);
let op =
AsyncIoOperation::write_from_vec(0x800, OwnedIoBuffer::from_vec(vec![0; 0x900]), 1);
assert!(matches!(
sync_io.submit_data_operation(op),
Err(AsyncIoError::WriteVectored(_))
));
}
#[test]
fn sync_accepts_operation_exactly_filling_logical_size() {
let file = TempFile::new().unwrap().into_file();
file.set_len(0x2000).unwrap();
let mut sync_io =
FixedVhdSync::new(AlignedFile::new(file.try_clone().unwrap(), false), 0x1000);
// end == size: boundary must be accepted
let op = AsyncIoOperation::read_to_vec(0, OwnedIoBuffer::from_vec(vec![0; 0x1000]), 1);
sync_io.submit_data_operation(op).unwrap();
}
#[test]
fn sync_accepts_operation_at_last_byte() {
let file = TempFile::new().unwrap().into_file();
file.set_len(0x2000).unwrap();
let mut sync_io =
FixedVhdSync::new(AlignedFile::new(file.try_clone().unwrap(), false), 0x1000);
// end = 0xFFF + 1 = 0x1000 == size: boundary must be accepted
let op = AsyncIoOperation::read_to_vec(0xFFF, OwnedIoBuffer::from_vec(vec![0; 1]), 1);
sync_io.submit_data_operation(op).unwrap();
}
#[cfg(feature = "io_uring")]
#[test]
fn io_uring_batch_rejects_request_straddling_logical_size() {
let file = TempFile::new().unwrap().into_file();
file.set_len(0x2000).unwrap();
let mut async_io = FixedVhdAsync::new(
AlignedFile::new(file.try_clone().unwrap(), false),
8,
0x1000,
)
.unwrap();
let op = AsyncIoOperation::read_to_vec(0x800, OwnedIoBuffer::from_vec(vec![0; 0x900]), 1);
assert!(matches!(
async_io.submit_batch_requests(vec![op]),
Err(AsyncIoError::ReadVectored(_))
));
}
#[cfg(feature = "io_uring")]
#[test]
fn io_uring_rejects_single_op_straddling_logical_size() {
let file = TempFile::new().unwrap().into_file();
file.set_len(0x2000).unwrap();
let mut async_io = FixedVhdAsync::new(
AlignedFile::new(file.try_clone().unwrap(), false),
8,
0x1000,
)
.unwrap();
let op = AsyncIoOperation::read_to_vec(0x800, OwnedIoBuffer::from_vec(vec![0; 0x900]), 1);
assert!(matches!(
async_io.submit_data_operation(op),
Err(AsyncIoError::ReadVectored(_))
));
}
#[test]
fn try_clone_preserves_sync_dispatch() {
let file = make_vhd_file();
let disk = VhdDisk::new(file, false, false).unwrap();
let cloned = disk.try_clone().unwrap();
assert_async_io_from_dyn(cloned.as_ref(), false);
}
#[cfg(feature = "io_uring")]
#[test]
fn try_clone_preserves_io_uring_dispatch() {
let file = make_vhd_file();
let disk = VhdDisk::new(file, true, false).unwrap();
let cloned = disk.try_clone().unwrap();
assert_async_io_from_dyn(cloned.as_ref(), true);
}
#[test]
fn resize_returns_error() {
let file = make_vhd_file();
let mut disk = VhdDisk::new(file, false, false).unwrap();
assert!(disk.resize(0x2000_0000).is_err());
}
#[test]
fn physical_size_includes_footer() {
let file = make_vhd_file();
let disk = VhdDisk::new(file, false, false).unwrap();
// Data region (0x1122_3344) + VHD footer (0x200).
assert_eq!(disk.physical_size().unwrap(), 0x1122_3344 + 0x200);
}
}

View File

@@ -2,29 +2,29 @@
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{self, Seek, SeekFrom};
use std::mem::size_of;
use std::os::unix::fs::FileExt;
use std::{io, result};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use byteorder::{ByteOrder, LittleEndian};
use remain::sorted;
use thiserror::Error;
use crate::vhdx::vhdx_header::RegionTableEntry;
use crate::vhdx::vhdx_metadata::DiskSpec;
use super::header::RegionTableEntry;
use super::metadata::DiskSpec;
use crate::aligned_file::AlignedFile;
// Payload BAT Entry States
pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
pub const PAYLOAD_BLOCK_UNDEFINED: u64 = 1;
pub const PAYLOAD_BLOCK_ZERO: u64 = 2;
pub const PAYLOAD_BLOCK_UNMAPPED: u64 = 3;
pub const PAYLOAD_BLOCK_FULLY_PRESENT: u64 = 6;
pub const PAYLOAD_BLOCK_PARTIALLY_PRESENT: u64 = 7;
pub(super) const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
pub(super) const PAYLOAD_BLOCK_UNDEFINED: u64 = 1;
pub(super) const PAYLOAD_BLOCK_ZERO: u64 = 2;
pub(super) const PAYLOAD_BLOCK_UNMAPPED: u64 = 3;
pub(super) const PAYLOAD_BLOCK_FULLY_PRESENT: u64 = 6;
pub(super) const PAYLOAD_BLOCK_PARTIALLY_PRESENT: u64 = 7;
// Mask for the BAT state
pub const BAT_STATE_BIT_MASK: u64 = 0x07;
pub(super) const BAT_STATE_BIT_MASK: u64 = 0x07;
// Mask for the offset within the file in units of 1 MB
pub const BAT_FILE_OFF_MASK: u64 = 0xFFFFFFFFFFF00000;
pub(super) const BAT_FILE_OFF_MASK: u64 = 0xFFFFFFFFFFF00000;
#[sorted]
#[derive(Error, Debug)]
@@ -39,15 +39,15 @@ pub enum VhdxBatError {
WriteBat(#[source] io::Error),
}
pub type Result<T> = std::result::Result<T, VhdxBatError>;
pub(super) type Result<T> = result::Result<T, VhdxBatError>;
#[derive(Default, Clone, Debug)]
pub struct BatEntry(pub u64);
pub(super) struct BatEntry(pub u64);
impl BatEntry {
// Read all BAT entries presented on the disk and insert them to a vector
pub fn collect_bat_entries(
f: &mut File,
pub(super) fn collect_bat_entries(
f: &AlignedFile,
disk_spec: &DiskSpec,
bat_entry: &RegionTableEntry,
) -> Result<Vec<BatEntry>> {
@@ -63,14 +63,10 @@ impl BatEntry {
let mut bat: Vec<BatEntry> = Vec::with_capacity(bat_entry.length as usize);
let offset = bat_entry.file_offset;
for i in 0..entry_count {
f.seek(SeekFrom::Start(offset + i * size_of::<u64>() as u64))
let mut entry = [0u8; size_of::<u64>()];
f.read_exact_at(&mut entry, offset + i * size_of::<u64>() as u64)
.map_err(VhdxBatError::ReadBat)?;
let bat_entry = BatEntry(
f.read_u64::<LittleEndian>()
.map_err(VhdxBatError::ReadBat)?,
);
bat.insert(i as usize, bat_entry);
bat.insert(i as usize, BatEntry(LittleEndian::read_u64(&entry)));
}
Ok(bat)
@@ -83,14 +79,12 @@ impl BatEntry {
}
// Routine for writing BAT entries to the disk
pub fn write_bat_entries(
f: &mut File,
pub(super) fn write_bat_entries(
f: &AlignedFile,
bat_offset: u64,
bat_entries: &[BatEntry],
) -> Result<()> {
for i in 0..bat_entries.len() as u64 {
f.seek(SeekFrom::Start(bat_offset + i * size_of::<u64>() as u64))
.map_err(VhdxBatError::WriteBat)?;
let bat_entry = match bat_entries.get(i as usize) {
Some(entry) => entry.0,
None => {
@@ -98,7 +92,9 @@ impl BatEntry {
}
};
f.write_u64::<LittleEndian>(bat_entry)
let mut buf = [0u8; size_of::<u64>()];
LittleEndian::write_u64(&mut buf, bat_entry);
f.write_all_at(&buf, bat_offset + i * size_of::<u64>() as u64)
.map_err(VhdxBatError::WriteBat)?;
}
Ok(())

View File

@@ -0,0 +1,208 @@
// Copyright © 2021 Intel Corporation
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0
use std::collections::VecDeque;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::sync::{Arc, Mutex};
use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
use crate::formats::vhdx::Vhdx;
pub(super) struct VhdxSync {
vhdx_file: Arc<Mutex<Vhdx>>,
eventfd: EventFd,
completion_list: VecDeque<AsyncIoCompletion>,
size: u64,
}
impl VhdxSync {
pub(super) fn new(vhdx_file: Arc<Mutex<Vhdx>>, size: u64) -> Self {
VhdxSync {
vhdx_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)
.expect("Failed creating EventFd for VhdxSync"),
completion_list: VecDeque::new(),
size,
}
}
fn read_operation(&mut self, op: &mut AsyncIoOperation) -> AsyncIoResult<usize> {
let offset = op.offset();
let mut buf = vec![0u8; op.total_len()];
let mut vhdx = self.vhdx_file.lock().unwrap();
vhdx.seek(SeekFrom::Start(offset as u64))
.map_err(AsyncIoError::ReadVectored)?;
let result = vhdx.read(&mut buf).map_err(AsyncIoError::ReadVectored)?;
drop(vhdx);
op.write_bytes_at(0, &buf[..result])
.map_err(AsyncIoError::ReadVectored)?;
Ok(result)
}
fn write_operation(&mut self, op: &AsyncIoOperation) -> AsyncIoResult<usize> {
let offset = op.offset();
let mut buf = vec![0u8; op.total_len()];
op.read_bytes_at(0, &mut buf)
.map_err(AsyncIoError::WriteVectored)?;
let mut vhdx = self.vhdx_file.lock().unwrap();
vhdx.seek(SeekFrom::Start(offset as u64))
.map_err(AsyncIoError::WriteVectored)?;
let result = vhdx.write(&buf).map_err(AsyncIoError::WriteVectored)?;
Ok(result)
}
}
impl AsyncIo for VhdxSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
}
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
op.validate_bounds(self.size)?;
let is_read = op.is_read();
let mut op = op;
let result = if is_read {
self.read_operation(&mut op)?
} else {
self.write_operation(&op)?
};
self.completion_list
.push_back(AsyncIoCompletion::from_operation(op, result as i32));
self.eventfd.write(1).unwrap();
Ok(())
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.vhdx_file
.lock()
.unwrap()
.flush()
.map_err(AsyncIoError::Fsync)?;
if let Some(user_data) = user_data {
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, 0, None));
self.eventfd.write(1).unwrap();
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
self.completion_list.pop_front()
}
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
Err(AsyncIoError::PunchHole(io::Error::other(
"punch_hole not supported for VHDX",
)))
}
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
Err(AsyncIoError::WriteZeroes(io::Error::other(
"write_zeroes not supported for VHDX",
)))
}
}
#[cfg(test)]
mod tests {
use std::fs;
use std::sync::{Arc, Mutex};
use vmm_sys_util::tempfile::TempFile;
use super::*;
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoOperation, OwnedIoBuffer};
use crate::formats::vhdx::Vhdx;
use crate::formats::vhdx::test_util::create_dynamic_vhdx;
fn make_vhdx_sync(tf: &TempFile) -> (VhdxSync, u64) {
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.open(tf.as_path())
.unwrap();
let vhdx = Vhdx::new(file, false).unwrap();
let size = vhdx.virtual_disk_size();
let sync = VhdxSync::new(Arc::new(Mutex::new(vhdx)), size);
(sync, size)
}
/// Builds a `VhdxSync` from a fresh 1 MiB dynamic VHDX, or `None`
/// if `qemu-img` is unavailable to generate one.
fn setup() -> Option<(VhdxSync, u64)> {
let tf = create_dynamic_vhdx(1)?;
Some(make_vhdx_sync(&tf))
}
#[test]
fn sync_rejects_read_straddling_logical_size() {
let Some((mut sync, size)) = setup() else {
eprintln!("skipping: qemu-img unavailable");
return;
};
let op = AsyncIoOperation::read_to_vec(
(size - 512) as i64,
OwnedIoBuffer::from_vec(vec![0u8; 1024]),
1,
);
assert!(matches!(
sync.submit_data_operation(op),
Err(AsyncIoError::ReadVectored(_))
));
}
#[test]
fn sync_rejects_write_straddling_logical_size() {
let Some((mut sync, size)) = setup() else {
eprintln!("skipping: qemu-img unavailable");
return;
};
let op = AsyncIoOperation::write_from_vec(
(size - 512) as i64,
OwnedIoBuffer::from_vec(vec![0u8; 1024]),
1,
);
assert!(matches!(
sync.submit_data_operation(op),
Err(AsyncIoError::WriteVectored(_))
));
}
#[test]
fn sync_accepts_operation_exactly_filling_logical_size() {
let Some((mut sync, size)) = setup() else {
eprintln!("skipping: qemu-img unavailable");
return;
};
let op =
AsyncIoOperation::read_to_vec(0, OwnedIoBuffer::from_vec(vec![0u8; size as usize]), 1);
sync.submit_data_operation(op).unwrap();
}
#[test]
fn sync_accepts_operation_at_last_sector() {
let Some((mut sync, size)) = setup() else {
eprintln!("skipping: qemu-img unavailable");
return;
};
// VHDX operates in 512-byte sectors; read exactly the last sector.
let op = AsyncIoOperation::read_to_vec(
(size - 512) as i64,
OwnedIoBuffer::from_vec(vec![0u8; 512]),
1,
);
sync.submit_data_operation(op).unwrap();
}
}

View File

@@ -3,14 +3,16 @@
// SPDX-License-Identifier: Apache-2.0
use std::collections::btree_map::BTreeMap;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use std::os::unix::fs::FileExt;
use std::{io, result};
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
use byteorder::{ByteOrder, LittleEndian};
use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
use zerocopy::{FromBytes, Immutable, IntoBytes};
use crate::aligned_file::AlignedFile;
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876; // "vhdxfile"
const HEADER_SIGN: u32 = 0x6461_6568; // "head"
@@ -19,7 +21,7 @@ const REGION_SIGN: u32 = 0x6967_6572; // "regi"
const FILE_START: u64 = 0; // The first element
const HEADER_1_START: u64 = 64 * 1024; // Header 1 start in Bytes
const HEADER_2_START: u64 = 128 * 1024; // Header 2 start in Bytes
pub const REGION_TABLE_1_START: u64 = 192 * 1024; // Region 1 start in Bytes
pub(super) const REGION_TABLE_1_START: u64 = 192 * 1024; // Region 1 start in Bytes
const REGION_TABLE_2_START: u64 = 256 * 1024; // Region 2 start in Bytes
const HEADER_SIZE: u64 = 4 * 1024; // Each header is 64 KiB, but only first 4 kiB contains info
@@ -27,8 +29,13 @@ const REGION_SIZE: u64 = 64 * 1024; // Each region size is 64 KiB
const REGION_ENTRY_REQUIRED: u32 = 1;
const BAT_GUID: &str = "2DC27766-F623-4200-9D64-115E9BFD4A08"; // BAT GUID
const MDR_GUID: &str = "8B7CA206-4790-4B9A-B8FE-575F050F886E"; // Metadata GUID
// VHDX stores GUIDs using little-endian GUID byte order.
const BAT_GUID: [u8; 16] = [
0x66, 0x77, 0xc2, 0x2d, 0x23, 0xf6, 0x00, 0x42, 0x9d, 0x64, 0x11, 0x5e, 0x9b, 0xfd, 0x4a, 0x08,
];
const MDR_GUID: [u8; 16] = [
0x06, 0xa2, 0x7c, 0x8b, 0x90, 0x47, 0x9a, 0x4b, 0xb8, 0xfe, 0x57, 0x5f, 0x05, 0x0f, 0x88, 0x6e,
];
#[sorted]
#[derive(Error, Debug)]
@@ -39,7 +46,7 @@ pub enum VhdxHeaderError {
DuplicateBATEntry,
#[error("Metadata region entry is not unique")]
DuplicateMDREntry,
#[error("Checksum doesn't match for")]
#[error("Checksum doesn't match for {0}")]
InvalidChecksum(String),
#[error("Invalid entry count")]
InvalidEntryCount,
@@ -47,8 +54,6 @@ pub enum VhdxHeaderError {
InvalidHeaderSign,
#[error("Not a valid VHDx region")]
InvalidRegionSign,
#[error("Couldn't parse Uuid for region entry {0}")]
InvalidUuid(#[source] uuid::Error),
#[error("Not a VHDx file")]
InvalidVHDXSign,
#[error("No valid header found")]
@@ -60,46 +65,39 @@ pub enum VhdxHeaderError {
#[error("Failed to read headers {0}")]
ReadHeader(#[source] io::Error),
#[error("Failed to read metadata {0}")]
ReadMetadata(#[source] std::io::Error),
ReadMetadata(#[source] io::Error),
#[error("Failed to read region table entries {0}")]
ReadRegionTableEntries(#[source] io::Error),
#[error("Failed to read region table header {0}")]
ReadRegionTableHeader(#[source] io::Error),
#[error("Failed to read region entries")]
RegionEntryCollectionFailed,
#[error("Region entry file offset ({0}) and length ({1}) overflow u64")]
RegionEntryOverflow(u64 /* start */, usize /* length */),
#[error("Overlapping regions found")]
RegionOverlap,
#[error("Reserved region has non-zero value")]
ReservedIsNonZero,
#[error("Failed to seek in File Type Identifier {0}")]
SeekFileTypeIdentifier(#[source] io::Error),
#[error("Failed to seek in headers {0}")]
SeekHeader(#[source] io::Error),
#[error("Failed to seek in region table entries {0}")]
SeekRegionTableEntries(#[source] io::Error),
#[error("Failed to seek in region table header {0}")]
SeekRegionTableHeader(#[source] io::Error),
#[error("We do not recognize this entry")]
UnrecognizedRegionEntry,
#[error("Failed to write header {0}")]
WriteHeader(#[source] io::Error),
}
pub type Result<T> = std::result::Result<T, VhdxHeaderError>;
pub(super) type Result<T> = result::Result<T, VhdxHeaderError>;
#[derive(Clone, Debug)]
pub struct FileTypeIdentifier {
pub(super) struct FileTypeIdentifier {
pub _signature: u64,
}
impl FileTypeIdentifier {
/// Reads the File Type Identifier structure from a reference VHDx file
pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
f.seek(SeekFrom::Start(FILE_START))
.map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
let _signature = f
.read_u64::<LittleEndian>()
pub(super) fn new(f: &AlignedFile) -> Result<FileTypeIdentifier> {
let mut buf = [0u8; size_of::<u64>()];
f.read_exact_at(&mut buf, FILE_START)
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
let _signature = LittleEndian::read_u64(&buf);
if _signature != VHDX_SIGN {
return Err(VhdxHeaderError::InvalidVHDXSign);
}
@@ -109,8 +107,8 @@ impl FileTypeIdentifier {
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct Header {
#[derive(Clone, Copy, Debug, FromBytes, Immutable, IntoBytes)]
pub(super) struct Header {
pub signature: u32,
pub checksum: u32,
pub sequence_number: u64,
@@ -125,17 +123,14 @@ pub struct Header {
impl Header {
/// Reads the Header structure from a reference VHDx file
pub fn new(f: &mut File, start: u64) -> Result<Header> {
pub(super) fn new(f: &AlignedFile, start: u64) -> Result<Header> {
// Read the whole header into a buffer. We will need it for
// calculating checksum.
let mut buffer = [0; HEADER_SIZE as usize];
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekHeader)?;
f.read_exact(&mut buffer)
f.read_exact_at(&mut buffer, start)
.map_err(VhdxHeaderError::ReadHeader)?;
// SAFETY: buffer is of correct size and has been successfully filled.
let header = unsafe { *(buffer.as_ptr() as *mut Header) };
let header = Header::read_from_prefix(&buffer).unwrap().0;
if header.signature != HEADER_SIGN {
return Err(VhdxHeaderError::InvalidHeaderSign);
}
@@ -148,33 +143,26 @@ impl Header {
Ok(header)
}
/// Converts the header structure into a buffer
fn write_to_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
// SAFETY: self is a valid header.
let reference = unsafe {
std::slice::from_raw_parts(self as *const Header as *const u8, HEADER_SIZE as usize)
};
*buffer = reference.try_into().unwrap();
}
/// Creates and returns new updated header from the provided current header
fn update_header(
f: &mut File,
f: &AlignedFile,
current_header: &Header,
change_data_guid: bool,
mut file_write_guid: u128,
file_write_guid: u128,
start: u64,
) -> Result<Header> {
let mut buffer = [0u8; HEADER_SIZE as usize];
let mut data_write_guid = current_header.data_write_guid;
let data_write_guid = if change_data_guid {
Uuid::new_v4().as_u128()
} else {
current_header.data_write_guid
};
if change_data_guid {
data_write_guid = Uuid::new_v4().as_u128();
}
if file_write_guid == 0 {
file_write_guid = current_header.file_write_guid;
}
let file_write_guid = if file_write_guid == 0 {
current_header.file_write_guid
} else {
file_write_guid
};
let mut new_header = Header {
signature: current_header.signature,
@@ -189,20 +177,19 @@ impl Header {
log_offset: current_header.log_offset,
};
new_header.write_to_buffer(&mut buffer);
new_header.write_to_prefix(&mut buffer).unwrap();
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
new_header.write_to_buffer(&mut buffer);
new_header.write_to_prefix(&mut buffer).unwrap();
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekHeader)?;
f.write(&buffer).map_err(VhdxHeaderError::WriteHeader)?;
f.write_all_at(&buffer, start)
.map_err(VhdxHeaderError::WriteHeader)?;
Ok(new_header)
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, FromBytes)]
struct RegionTableHeader {
pub signature: u32,
pub checksum: u32,
@@ -212,17 +199,14 @@ struct RegionTableHeader {
impl RegionTableHeader {
/// Reads the Region Table Header structure from a reference VHDx file
pub fn new(f: &mut File, start: u64) -> Result<RegionTableHeader> {
pub(crate) fn new(f: &AlignedFile, start: u64) -> Result<RegionTableHeader> {
// Read the whole header into a buffer. We will need it for calculating
// checksum.
let mut buffer = [0u8; REGION_SIZE as usize];
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekRegionTableHeader)?;
f.read_exact(&mut buffer)
f.read_exact_at(&mut buffer, start)
.map_err(VhdxHeaderError::ReadRegionTableHeader)?;
// SAFETY: buffer is of correct size and has been successfully filled.
let region_table_header = unsafe { *(buffer.as_ptr() as *mut RegionTableHeader) };
let region_table_header = RegionTableHeader::read_from_prefix(&buffer).unwrap().0;
if region_table_header.signature != REGION_SIGN {
return Err(VhdxHeaderError::InvalidRegionSign);
}
@@ -244,7 +228,13 @@ impl RegionTableHeader {
}
}
pub struct RegionInfo {
/// Returns `true` if the half-open byte ranges `[a_start, a_end)` and
/// `[b_start, b_end)` overlap.
fn ranges_overlap(a_start: u64, a_end: u64, b_start: u64, b_end: u64) -> bool {
a_start < b_end && b_start < a_end
}
pub(super) struct RegionInfo {
pub bat_entry: RegionTableEntry,
pub mdr_entry: RegionTableEntry,
pub region_entries: BTreeMap<u64, u64>,
@@ -253,7 +243,7 @@ pub struct RegionInfo {
impl RegionInfo {
/// Collect all entries in a BTreeMap from the Region Table and identifies
/// BAT and metadata regions
pub fn new(f: &mut File, region_start: u64, entry_count: u32) -> Result<RegionInfo> {
pub(super) fn new(f: &AlignedFile, region_start: u64, entry_count: u32) -> Result<RegionInfo> {
let mut bat_entry: Option<RegionTableEntry> = None;
let mut mdr_entry: Option<RegionTableEntry> = None;
@@ -261,31 +251,34 @@ impl RegionInfo {
let mut region_entries = BTreeMap::new();
let mut buffer = [0; REGION_SIZE as usize];
// Seek after the Region Table Header
f.seek(SeekFrom::Start(
// Read after the Region Table Header
f.read_exact_at(
&mut buffer,
region_start + size_of::<RegionTableHeader>() as u64,
))
.map_err(VhdxHeaderError::SeekRegionTableEntries)?;
f.read_exact(&mut buffer)
.map_err(VhdxHeaderError::ReadRegionTableEntries)?;
)
.map_err(VhdxHeaderError::ReadRegionTableEntries)?;
for _ in 0..entry_count {
let entry =
RegionTableEntry::new(&buffer[offset..offset + size_of::<RegionTableEntry>()])?;
let entry = RegionTableEntry::read_from_bytes(
&buffer[offset..offset + size_of::<RegionTableEntry>()],
)
.unwrap();
offset += size_of::<RegionTableEntry>();
let start = entry.file_offset;
let end = start + entry.length as u64;
let end = start.checked_add(entry.length as u64).ok_or(
VhdxHeaderError::RegionEntryOverflow(start, entry.length as usize),
)?;
for (region_ent_start, region_ent_end) in region_entries.iter() {
if !((start >= *region_ent_start) || (end <= *region_ent_end)) {
if ranges_overlap(start, end, *region_ent_start, *region_ent_end) {
return Err(VhdxHeaderError::RegionOverlap);
}
}
region_entries.insert(entry.file_offset, entry.file_offset + entry.length as u64);
region_entries.insert(start, end);
if entry.guid == Uuid::parse_str(BAT_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
if entry.guid == BAT_GUID {
if bat_entry.is_none() {
bat_entry = Some(entry);
continue;
@@ -293,7 +286,7 @@ impl RegionInfo {
return Err(VhdxHeaderError::DuplicateBATEntry);
}
if entry.guid == Uuid::parse_str(MDR_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
if entry.guid == MDR_GUID {
if mdr_entry.is_none() {
mdr_entry = Some(entry);
continue;
@@ -327,28 +320,14 @@ impl RegionInfo {
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct RegionTableEntry {
pub guid: Uuid,
#[derive(Clone, Copy, Debug, FromBytes)]
pub(super) struct RegionTableEntry {
guid: [u8; 16],
pub file_offset: u64,
pub length: u32,
pub required: u32,
}
impl RegionTableEntry {
/// Reads one Region Entry from a Region Table index that starts from 0
pub fn new(buffer: &[u8]) -> Result<RegionTableEntry> {
assert!(buffer.len() == std::mem::size_of::<RegionTableEntry>());
// SAFETY: the assertion above makes sure the buffer size is correct.
let mut region_table_entry = unsafe { *(buffer.as_ptr() as *mut RegionTableEntry) };
let uuid = crate::vhdx::uuid_from_guid(buffer);
region_table_entry.guid = uuid;
Ok(region_table_entry)
}
}
enum HeaderNo {
First,
Second,
@@ -356,7 +335,7 @@ enum HeaderNo {
/// Contains the information from the header of a VHDx file
#[derive(Clone, Debug)]
pub struct VhdxHeader {
pub(super) struct VhdxHeader {
_file_type_identifier: FileTypeIdentifier,
header_1: Header,
header_2: Header,
@@ -366,7 +345,7 @@ pub struct VhdxHeader {
impl VhdxHeader {
/// Creates a VhdxHeader from a reference to a file
pub fn new(f: &mut File) -> Result<VhdxHeader> {
pub(super) fn new(f: &AlignedFile) -> Result<VhdxHeader> {
Ok(VhdxHeader {
_file_type_identifier: FileTypeIdentifier::new(f)?,
header_1: Header::new(f, HEADER_1_START)?,
@@ -403,7 +382,7 @@ impl VhdxHeader {
/// current one. Returns both headers as a tuple sequenced the way it was
/// received from the parameter list.
fn update_header(
f: &mut File,
f: &AlignedFile,
header_1: Result<Header>,
header_2: Result<Header>,
guid: u128,
@@ -426,7 +405,7 @@ impl VhdxHeader {
// Update the provided headers according to the spec
fn update_headers(
f: &mut File,
f: &AlignedFile,
header_1: Result<Header>,
header_2: Result<Header>,
guid: u128,
@@ -436,14 +415,14 @@ impl VhdxHeader {
VhdxHeader::update_header(f, Ok(header_1), Ok(header_2), guid)
}
pub fn update(&mut self, f: &mut File) -> Result<()> {
pub(super) fn update(&mut self, f: &AlignedFile) -> Result<()> {
let headers = VhdxHeader::update_headers(f, Ok(self.header_1), Ok(self.header_2), 0)?;
self.header_1 = headers.0;
self.header_2 = headers.1;
Ok(())
}
pub fn region_entry_count(&self) -> u32 {
pub(super) fn region_entry_count(&self) -> u32 {
self.region_table_1.entry_count
}
}
@@ -467,3 +446,144 @@ fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
new_csum
}
#[cfg(test)]
mod tests {
use std::os::unix::fs::FileExt;
use vmm_sys_util::tempfile::TempFile;
use zerocopy::{FromBytes, IntoBytes};
use super::{
BAT_GUID, HEADER_SIGN, Header, MDR_GUID, REGION_TABLE_1_START, RegionInfo,
RegionTableHeader, VhdxHeaderError, ranges_overlap,
};
use crate::aligned_file::AlignedFile;
#[test]
fn test_header_bytes_round_trip() {
let header = Header {
signature: HEADER_SIGN,
checksum: 0x1122_3344,
sequence_number: 0x0102_0304_0506_0708,
file_write_guid: 0x0f0e_0d0c_0b0a_0908_0706_0504_0302_0100,
data_write_guid: 0x1f1e_1d1c_1b1a_1918_1716_1514_1312_1110,
log_guid: 0x2f2e_2d2c_2b2a_2928_2726_2524_2322_2120,
log_version: 0xabcd,
version: 0x0001,
log_length: 0x0010_0000,
log_offset: 0x0000_0100_0000_0000,
};
let bytes = header.as_bytes();
assert_eq!(&bytes[0..4], &header.signature.to_le_bytes()[..]);
assert_eq!(&bytes[8..16], &header.sequence_number.to_le_bytes()[..]);
assert_eq!(&bytes[16..32], &header.file_write_guid.to_le_bytes()[..]);
assert_eq!(&bytes[64..66], &header.log_version.to_le_bytes()[..]);
assert_eq!(&bytes[72..80], &header.log_offset.to_le_bytes()[..]);
let parsed = Header::read_from_bytes(bytes).unwrap();
assert_eq!({ parsed.signature }, { header.signature });
assert_eq!({ parsed.checksum }, { header.checksum });
assert_eq!({ parsed.sequence_number }, { header.sequence_number });
assert_eq!({ parsed.file_write_guid }, { header.file_write_guid });
assert_eq!({ parsed.data_write_guid }, { header.data_write_guid });
assert_eq!({ parsed.log_guid }, { header.log_guid });
assert_eq!({ parsed.log_version }, { header.log_version });
assert_eq!({ parsed.version }, { header.version });
assert_eq!({ parsed.log_length }, { header.log_length });
assert_eq!({ parsed.log_offset }, { header.log_offset });
}
#[test]
fn test_ranges_overlap() {
// (new [start,end), existing [s,e), expected overlap)
let cases: &[(u64, u64, u64, u64, bool)] = &[
// Genuine overlaps — all of these must be detected.
(0, 10, 0, 10, true), // identical
(2, 8, 0, 10, true), // new fully inside existing
(0, 20, 5, 10, true), // new fully contains existing
(5, 15, 0, 10, true), // partial, new starts inside existing
(0, 8, 5, 15, true), // partial, new starts before existing
// Non-overlapping — must not be flagged.
(0, 5, 10, 20, false), // disjoint, new before existing
(30, 40, 10, 20, false), // disjoint, new after existing
(0, 10, 10, 20, false), // touching at the boundary (half-open)
];
for &(a_start, a_end, b_start, b_end, expected) in cases {
assert_eq!(
ranges_overlap(a_start, a_end, b_start, b_end),
expected,
"[{a_start},{a_end}) vs [{b_start},{b_end})"
);
// Overlap is symmetric.
assert_eq!(
ranges_overlap(b_start, b_end, a_start, a_end),
expected,
"symmetry: [{b_start},{b_end}) vs [{a_start},{a_end})"
);
}
}
/// Builds the 32-byte on-disk region table entry for `guid` describing
/// the region `[file_offset, file_offset + length)`.
fn region_entry(guid: [u8; 16], file_offset: u64, length: u32) -> [u8; 32] {
let mut e = [0u8; 32];
e[0..16].copy_from_slice(&guid);
e[16..24].copy_from_slice(&file_offset.to_le_bytes());
e[24..28].copy_from_slice(&length.to_le_bytes());
// `required` (e[28..32]) left zero.
e
}
#[test]
fn test_region_info_rejects_overlapping_regions() {
// BAT region [1 MiB, 3 MiB) and metadata region [2 MiB, 4 MiB) overlap
// on [2 MiB, 3 MiB); per [MS-VHDX] all region objects must be
// non-overlapping, so this image must be rejected.
const MIB: u64 = 1024 * 1024;
let region_start = REGION_TABLE_1_START;
let entries_at = region_start + size_of::<RegionTableHeader>() as u64;
let temp = TempFile::new().unwrap();
let f = temp.into_file();
f.set_len(entries_at + 64 * 1024).unwrap();
f.write_all_at(&region_entry(BAT_GUID, MIB, (2 * MIB) as u32), entries_at)
.unwrap();
f.write_all_at(
&region_entry(MDR_GUID, 2 * MIB, (2 * MIB) as u32),
entries_at + 32,
)
.unwrap();
let af = AlignedFile::new(f, false);
let res = RegionInfo::new(&af, region_start, 2);
assert!(
matches!(res, Err(VhdxHeaderError::RegionOverlap)),
"expected RegionOverlap for an overlapping region table"
);
}
#[test]
fn test_region_info_rejects_overflowing_region() {
// A region whose file offset plus length wraps past u64::MAX must be
// rejected rather than silently producing a small end offset that
// could mask a genuine overlap.
let region_start = REGION_TABLE_1_START;
let entries_at = region_start + size_of::<RegionTableHeader>() as u64;
let temp = TempFile::new().unwrap();
let f = temp.into_file();
f.set_len(entries_at + 64 * 1024).unwrap();
f.write_all_at(&region_entry(BAT_GUID, u64::MAX, 0x1000), entries_at)
.unwrap();
let af = AlignedFile::new(f, false);
let res = RegionInfo::new(&af, region_start, 1);
assert!(
matches!(res, Err(VhdxHeaderError::RegionEntryOverflow(..))),
"expected RegionEntryOverflow for a wrapping region entry"
);
}
}

View File

@@ -2,14 +2,15 @@
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::os::unix::fs::FileExt;
use std::{io, result};
use remain::sorted;
use thiserror::Error;
use crate::vhdx::vhdx_bat::{self, BatEntry, VhdxBatError};
use crate::vhdx::vhdx_metadata::{self, DiskSpec};
use super::bat::{self, BatEntry, VhdxBatError};
use super::metadata::{self, DiskSpec};
use crate::aligned_file::AlignedFile;
const SECTOR_SIZE: u64 = 512;
@@ -32,7 +33,7 @@ pub enum VhdxIoError {
WriteBat(#[source] VhdxBatError),
}
pub type Result<T> = std::result::Result<T, VhdxIoError>;
pub(super) type Result<T> = result::Result<T, VhdxIoError>;
macro_rules! align {
($n:expr, $align:expr) => {{ $n.div_ceil($align) * $align }};
@@ -50,7 +51,7 @@ struct Sector {
impl Sector {
/// Translate sector index and count of data in file to actual offsets and
/// BAT index.
pub fn new(
pub(crate) fn new(
disk_spec: &DiskSpec,
bat: &[BatEntry],
sector_index: u64,
@@ -74,7 +75,7 @@ impl Sector {
return Err(VhdxIoError::InvalidBatIndex);
}
};
sector.file_offset = bat_entry & vhdx_bat::BAT_FILE_OFF_MASK;
sector.file_offset = bat_entry & bat::BAT_FILE_OFF_MASK;
if sector.file_offset != 0 {
sector.file_offset += sector.block_offset;
}
@@ -85,8 +86,8 @@ impl Sector {
/// VHDx IO read routine: requires relative sector index and count for the
/// requested data.
pub fn read(
f: &mut File,
pub(super) fn read(
f: &AlignedFile,
buf: &mut [u8],
disk_spec: &DiskSpec,
bat: &[BatEntry],
@@ -108,21 +109,20 @@ pub fn read(
}
};
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
f.seek(SeekFrom::Start(sector.file_offset))
.map_err(VhdxIoError::ReadSectorBlock)?;
f.read_exact(
match bat_entry & bat::BAT_STATE_BIT_MASK {
bat::PAYLOAD_BLOCK_NOT_PRESENT
| bat::PAYLOAD_BLOCK_UNDEFINED
| bat::PAYLOAD_BLOCK_UNMAPPED
| bat::PAYLOAD_BLOCK_ZERO => {}
bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
f.read_exact_at(
&mut buf
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
sector.file_offset,
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {
@@ -138,8 +138,8 @@ pub fn read(
/// VHDx IO write routine: requires relative sector index and count for the
/// requested data.
pub fn write(
f: &mut File,
pub(super) fn write(
f: &AlignedFile,
buf: &[u8],
disk_spec: &mut DiskSpec,
bat_offset: u64,
@@ -162,49 +162,48 @@ pub fn write(
}
};
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
let file_offset =
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
match bat_entry & bat::BAT_STATE_BIT_MASK {
bat::PAYLOAD_BLOCK_NOT_PRESENT
| bat::PAYLOAD_BLOCK_UNDEFINED
| bat::PAYLOAD_BLOCK_UNMAPPED
| bat::PAYLOAD_BLOCK_ZERO => {
let file_offset = align!(disk_spec.image_size, metadata::BLOCK_SIZE_MIN as u64);
let new_size = file_offset
.checked_add(disk_spec.block_size as u64)
.ok_or(VhdxIoError::InvalidDiskSize)?;
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
f.file()
.set_len(new_size)
.map_err(VhdxIoError::ResizeFile)?;
disk_spec.image_size = new_size;
let new_bat_entry = file_offset
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
let new_bat_entry =
file_offset | (bat::PAYLOAD_BLOCK_FULLY_PRESENT & bat::BAT_STATE_BIT_MASK);
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
BatEntry::write_bat_entries(f, bat_offset, bat).map_err(VhdxIoError::WriteBat)?;
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
if file_offset < metadata::BLOCK_SIZE_MIN as u64 {
break;
}
f.seek(SeekFrom::Start(file_offset))
.map_err(VhdxIoError::ReadSectorBlock)?;
f.write_all(
f.write_all_at(
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
file_offset,
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
if sector.file_offset < metadata::BLOCK_SIZE_MIN as u64 {
break;
}
f.seek(SeekFrom::Start(sector.file_offset))
.map_err(VhdxIoError::ReadSectorBlock)?;
f.write_all(
f.write_all_at(
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
sector.file_offset,
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {

View File

@@ -2,16 +2,16 @@
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::mem::size_of;
use std::os::unix::fs::FileExt;
use std::{io, result};
use byteorder::{LittleEndian, ReadBytesExt};
use byteorder::{ByteOrder, LittleEndian};
use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
use zerocopy::FromBytes;
use crate::vhdx::vhdx_header::RegionTableEntry;
use super::header::RegionTableEntry;
use crate::aligned_file::AlignedFile;
const METADATA_SIGN: u64 = 0x6174_6164_6174_656D;
const METADATA_ENTRY_SIZE: usize = 32;
@@ -21,19 +21,31 @@ const METADATA_TABLE_MAX_SIZE: usize = METADATA_ENTRY_SIZE * (METADATA_MAX_ENTRI
const METADATA_FLAGS_IS_REQUIRED: u32 = 0x04;
pub const BLOCK_SIZE_MIN: u32 = 1 << 20; // 1 MiB
pub(super) const BLOCK_SIZE_MIN: u32 = 1 << 20; // 1 MiB
const BLOCK_SIZE_MAX: u32 = 256 << 20; // 256 MiB
const MAX_SECTORS_PER_BLOCK: u64 = 1 << 23;
const BLOCK_HAS_PARENT: u32 = 0x02; // Has a parent or a backing file
// GUID for known metadata items
const METADATA_FILE_PARAMETER: &str = "CAA16737-FA36-4D43-B3B6-33F0AA44E76B";
const METADATA_VIRTUAL_DISK_SIZE: &str = "2FA54224-CD1B-4876-B211-5DBED83BF4B8";
const METADATA_VIRTUAL_DISK_ID: &str = "BECA12AB-B2E6-4523-93EF-C309E000C746";
const METADATA_LOGICAL_SECTOR_SIZE: &str = "8141BF1D-A96F-4709-BA47-F233A8FAAB5F";
const METADATA_PHYSICAL_SECTOR_SIZE: &str = "CDA348C7-445D-4471-9CC9-E9885251C556";
const METADATA_PARENT_LOCATOR: &str = "A8D35F2D-B30B-454D-ABF7-D3D84834AB0C";
const METADATA_FILE_PARAMETER: [u8; 16] = [
0x37, 0x67, 0xa1, 0xca, 0x36, 0xfa, 0x43, 0x4d, 0xb3, 0xb6, 0x33, 0xf0, 0xaa, 0x44, 0xe7, 0x6b,
];
const METADATA_VIRTUAL_DISK_SIZE: [u8; 16] = [
0x24, 0x42, 0xa5, 0x2f, 0x1b, 0xcd, 0x76, 0x48, 0xb2, 0x11, 0x5d, 0xbe, 0xd8, 0x3b, 0xf4, 0xb8,
];
const METADATA_VIRTUAL_DISK_ID: [u8; 16] = [
0xab, 0x12, 0xca, 0xbe, 0xe6, 0xb2, 0x23, 0x45, 0x93, 0xef, 0xc3, 0x09, 0xe0, 0x00, 0xc7, 0x46,
];
const METADATA_LOGICAL_SECTOR_SIZE: [u8; 16] = [
0x1d, 0xbf, 0x41, 0x81, 0x6f, 0xa9, 0x09, 0x47, 0xba, 0x47, 0xf2, 0x33, 0xa8, 0xfa, 0xab, 0x5f,
];
const METADATA_PHYSICAL_SECTOR_SIZE: [u8; 16] = [
0xc7, 0x48, 0xa3, 0xcd, 0x5d, 0x44, 0x71, 0x44, 0x9c, 0xc9, 0xe9, 0x88, 0x52, 0x51, 0xc5, 0x56,
];
const METADATA_PARENT_LOCATOR: [u8; 16] = [
0x2d, 0x5f, 0xd3, 0xa8, 0x0b, 0xb3, 0x4d, 0x45, 0xab, 0xf7, 0xd3, 0xd8, 0x48, 0x34, 0xab, 0x0c,
];
const METADATA_FILE_PARAMETER_PRESENT: u16 = 0x01;
const METADATA_VIRTUAL_DISK_SIZE_PRESENT: u16 = 0x02;
@@ -69,8 +81,6 @@ pub enum VhdxMetadataError {
InvalidMetadataSign,
#[error("Invalid physical sector size")]
InvalidPhysicalSectorSize,
#[error("Invalid UUID")]
InvalidUuid(#[source] uuid::Error),
#[error("Invalid value")]
InvalidValue,
#[error("Not all required metadata found")]
@@ -83,10 +93,10 @@ pub enum VhdxMetadataError {
UnsupportedFlag,
}
pub type Result<T> = std::result::Result<T, VhdxMetadataError>;
pub(super) type Result<T> = result::Result<T, VhdxMetadataError>;
#[derive(Default, Clone, Debug)]
pub struct DiskSpec {
pub(super) struct DiskSpec {
pub disk_id: u128,
pub image_size: u64,
pub block_size: u32,
@@ -102,17 +112,18 @@ pub struct DiskSpec {
impl DiskSpec {
/// Parse all metadata from the provided file and store info in DiskSpec
/// structure.
pub fn new(f: &mut File, metadata_region: &RegionTableEntry) -> Result<DiskSpec> {
pub(super) fn new(f: &AlignedFile, metadata_region: &RegionTableEntry) -> Result<DiskSpec> {
let mut disk_spec = DiskSpec::default();
let mut metadata_presence: u16 = 0;
let mut offset = 0;
let metadata = f.metadata().map_err(VhdxMetadataError::ReadMetadata)?;
let metadata = f
.file()
.metadata()
.map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.image_size = metadata.len();
let mut buffer = [0u8; METADATA_TABLE_MAX_SIZE];
f.seek(SeekFrom::Start(metadata_region.file_offset))
.map_err(VhdxMetadataError::ReadMetadata)?;
f.read_exact(&mut buffer)
f.read_exact_at(&mut buffer, metadata_region.file_offset)
.map_err(VhdxMetadataError::ReadMetadata)?;
let metadata_header =
@@ -123,18 +134,13 @@ impl DiskSpec {
let metadata_entry =
MetadataTableEntry::new(&buffer[offset..offset + size_of::<MetadataTableEntry>()])?;
f.seek(SeekFrom::Start(
metadata_region.file_offset + metadata_entry.offset as u64,
))
.map_err(VhdxMetadataError::ReadMetadata)?;
let item_offset = metadata_region.file_offset + metadata_entry.offset as u64;
if metadata_entry.item_id
== Uuid::parse_str(METADATA_FILE_PARAMETER)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.block_size = f
.read_u32::<LittleEndian>()
if metadata_entry.item_id == METADATA_FILE_PARAMETER {
let mut item = [0u8; 2 * size_of::<u32>()];
f.read_exact_at(&mut item, item_offset)
.map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.block_size = LittleEndian::read_u32(&item[0..4]);
// MUST be at least 1 MiB and not greater than 256 MiB
if disk_spec.block_size < BLOCK_SIZE_MIN || disk_spec.block_size > BLOCK_SIZE_MAX {
@@ -146,50 +152,40 @@ impl DiskSpec {
return Err(VhdxMetadataError::InvalidBlockSize);
}
let bits = f
.read_u32::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
let bits = LittleEndian::read_u32(&item[4..8]);
disk_spec.has_parent = bits & BLOCK_HAS_PARENT != 0;
metadata_presence |= METADATA_FILE_PARAMETER_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_VIRTUAL_DISK_SIZE)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.virtual_disk_size = f
.read_u64::<LittleEndian>()
} else if metadata_entry.item_id == METADATA_VIRTUAL_DISK_SIZE {
let mut item = [0u8; size_of::<u64>()];
f.read_exact_at(&mut item, item_offset)
.map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.virtual_disk_size = LittleEndian::read_u64(&item);
metadata_presence |= METADATA_VIRTUAL_DISK_SIZE_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_VIRTUAL_DISK_ID)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.disk_id = f
.read_u128::<LittleEndian>()
} else if metadata_entry.item_id == METADATA_VIRTUAL_DISK_ID {
let mut item = [0u8; size_of::<u128>()];
f.read_exact_at(&mut item, item_offset)
.map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.disk_id = LittleEndian::read_u128(&item);
metadata_presence |= METADATA_VIRTUAL_DISK_ID_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_LOGICAL_SECTOR_SIZE)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.logical_sector_size = f
.read_u32::<LittleEndian>()
} else if metadata_entry.item_id == METADATA_LOGICAL_SECTOR_SIZE {
let mut item = [0u8; size_of::<u32>()];
f.read_exact_at(&mut item, item_offset)
.map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.logical_sector_size = LittleEndian::read_u32(&item);
if !(disk_spec.logical_sector_size == 512 || disk_spec.logical_sector_size == 4096)
{
return Err(VhdxMetadataError::InvalidLogicalSectorSize);
}
metadata_presence |= METADATA_LOGICAL_SECTOR_SIZE_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_PHYSICAL_SECTOR_SIZE)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.physical_sector_size = f
.read_u32::<LittleEndian>()
} else if metadata_entry.item_id == METADATA_PHYSICAL_SECTOR_SIZE {
let mut item = [0u8; size_of::<u32>()];
f.read_exact_at(&mut item, item_offset)
.map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.physical_sector_size = LittleEndian::read_u32(&item);
if !(disk_spec.physical_sector_size == 512
|| disk_spec.physical_sector_size == 4096)
{
@@ -197,10 +193,7 @@ impl DiskSpec {
}
metadata_presence |= METADATA_PHYSICAL_SECTOR_SIZE_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_PARENT_LOCATOR)
.map_err(VhdxMetadataError::InvalidUuid)?
{
} else if metadata_entry.item_id == METADATA_PARENT_LOCATOR {
metadata_presence |= METADATA_PARENT_LOCATOR_PRESENT;
} else {
return Err(VhdxMetadataError::InvalidMetadataItem);
@@ -268,7 +261,7 @@ impl DiskSpec {
}
#[repr(C, packed)]
#[derive(Default, Debug, Clone, Copy)]
#[derive(Default, Debug, Clone, Copy, FromBytes)]
struct MetadataTableHeader {
signature: u64,
reserved: u16,
@@ -277,10 +270,8 @@ struct MetadataTableHeader {
}
impl MetadataTableHeader {
pub fn new(buffer: &[u8]) -> Result<MetadataTableHeader> {
assert!(buffer.len() == std::mem::size_of::<MetadataTableHeader>());
// SAFETY: the assertion above makes sure the buffer size is correct.
let metadata_table_header = unsafe { *(buffer.as_ptr() as *mut MetadataTableHeader) };
pub(crate) fn new(buffer: &[u8]) -> Result<MetadataTableHeader> {
let metadata_table_header = MetadataTableHeader::read_from_bytes(buffer).unwrap();
if metadata_table_header.signature != METADATA_SIGN {
return Err(VhdxMetadataError::InvalidMetadataSign);
@@ -299,9 +290,9 @@ impl MetadataTableHeader {
}
#[repr(C, packed)]
#[derive(Default, Debug, Clone, Copy)]
pub struct MetadataTableEntry {
item_id: Uuid,
#[derive(Default, Debug, Clone, Copy, FromBytes)]
pub(super) struct MetadataTableEntry {
item_id: [u8; 16],
offset: u32,
length: u32,
flag_bits: u32,
@@ -311,12 +302,7 @@ pub struct MetadataTableEntry {
impl MetadataTableEntry {
/// Parse one metadata entry from the buffer
fn new(buffer: &[u8]) -> Result<MetadataTableEntry> {
assert!(buffer.len() == std::mem::size_of::<MetadataTableEntry>());
// SAFETY: the assertion above makes sure the buffer size is correct.
let mut metadata_table_entry = unsafe { *(buffer.as_ptr() as *mut MetadataTableEntry) };
let uuid = crate::vhdx::uuid_from_guid(buffer);
metadata_table_entry.item_id = uuid;
let metadata_table_entry = MetadataTableEntry::read_from_bytes(buffer).unwrap();
if metadata_table_entry.length > METADATA_LENGTH_MAX {
return Err(VhdxMetadataError::InvalidMetadataLength);

View File

@@ -0,0 +1,118 @@
// Copyright © 2021 Intel Corporation
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//! VHDX disk format support.
//!
//! Provides [`VhdxDisk`], the `DiskFile` wrapper for dynamic VHDX
//! images.
mod bat;
mod engine_sync;
mod header;
mod io;
mod metadata;
mod parser;
#[cfg(test)]
mod test_util;
use std::fs::File;
use std::io::Error as IoError;
use std::os::fd::AsRawFd;
use std::sync::{Arc, Mutex};
pub use parser::{Vhdx, VhdxError};
use self::engine_sync::VhdxSync;
use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError};
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
use crate::{Error, disk_file};
#[derive(Debug)]
pub struct VhdxDisk {
// FIXME: The Mutex serializes all VHDX I/O operations across queues, which
// is necessary for correctness but eliminates any parallelism benefit from
// multiqueue. Vhdx::clone() shares the underlying file description across
// threads, so concurrent I/O from multiple queues races on the file offset
// causing data corruption.
//
// A proper fix would require restructuring the VHDX I/O path so that data
// operations can proceed in parallel with independent file descriptors.
vhdx_file: Arc<Mutex<Vhdx>>,
}
impl VhdxDisk {
pub fn new(f: File, direct_io: bool) -> BlockResult<Self> {
Ok(VhdxDisk {
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f, direct_io).map_err(|e| {
let kind = match &e {
VhdxError::NotVhdx(_)
| VhdxError::ParseVhdxHeader(_)
| VhdxError::ParseVhdxMetadata(_)
| VhdxError::ParseVhdxRegionEntry(_) => BlockErrorKind::InvalidFormat,
VhdxError::ReadBatEntry(_) => BlockErrorKind::CorruptImage,
VhdxError::ReadFailed(_) | VhdxError::WriteFailed(_) => BlockErrorKind::Io,
};
BlockError::new(kind, e).with_op(ErrorOp::Open)
})?)),
})
}
}
impl disk_file::DiskSize for VhdxDisk {
fn logical_size(&self) -> BlockResult<u64> {
Ok(self.vhdx_file.lock().unwrap().virtual_disk_size())
}
}
impl disk_file::PhysicalSize for VhdxDisk {
fn physical_size(&self) -> BlockResult<u64> {
self.vhdx_file
.lock()
.unwrap()
.physical_size()
.map_err(|e| match e {
Error::GetFileMetadata(io) => {
BlockError::new(BlockErrorKind::Io, Error::GetFileMetadata(io))
}
_ => unreachable!("unexpected error from Vhdx::physical_size(): {e}"),
})
}
}
impl disk_file::DiskFd for VhdxDisk {
fn fd(&self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.vhdx_file.lock().unwrap().as_raw_fd())
}
}
impl disk_file::Geometry for VhdxDisk {}
impl disk_file::SparseCapable for VhdxDisk {}
impl disk_file::Resizable for VhdxDisk {
fn resize(&mut self, _size: u64) -> BlockResult<()> {
Err(BlockError::new(
BlockErrorKind::UnsupportedFeature,
DiskFileError::ResizeError(IoError::other("resize not supported for VHDX")),
)
.with_op(ErrorOp::Resize))
}
}
impl disk_file::DiskFile for VhdxDisk {}
impl disk_file::AsyncDiskFile for VhdxDisk {
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
Ok(Box::new(VhdxDisk {
vhdx_file: Arc::clone(&self.vhdx_file),
}))
}
fn create_async_io(&self, _ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
let size = self.vhdx_file.lock().unwrap().virtual_disk_size();
Ok(Box::new(VhdxSync::new(Arc::clone(&self.vhdx_file), size)))
}
}

Some files were not shown because too many files have changed in this diff Show More